3

I have written the following script, which runs from my user's crontab, parses ~/.ssh/config for SSH port forwards, and automatically connects them if the Host line ends with "remote," "local," or "dynamic."

Example ~/.ssh/config blocks:

Host localsocksdynamic
     DynamicForward 8080
     Hostname 1.2.3.4

Host localwebserverremote
     RemoteForward *:8080 localhost:80
     Hostname 1.2.3.4

Host indirectaccesslocal
     LocalForward 2222 2.3.4.5:80
     Hostname 1.2.3.4

Script:

#!/bin/sh

port_forwards=$(awk '/Host .*(remote|local|dynamic)/{printf $2} " "}' ~/.ssh/config  
for forward in ${port_forwards}
do
     port=$(awk "/${forward}/{f=1;next}/Host /{f=0}f" ~/.ssh/config | awk '/(Remote|Local|Dynamic)Forward/{print $2}' | cut -d':' -f2)
     [ -z ${port} ] || nc -z 127.0.0.1 ${port} || ssh -fqN ${forward} > /dev/null 2>&1
done

It works fine, but I was wondering whether it was possible to combine the awk commands in the port= line so that it's not necessary to pipe one awk to another. After a few hours of playing around and trying to understand the syntax I'm just not grokking it.

I know autossh exists and is a better way to do what I'm doing, but this is a learning experience and attempt to accomplish the same task without installing a package.

3

You can perform all the text processing with a single AWK invocation, simplified since you only care about the forwarding:

#!/bin/sh

awk '/^Host / { host = $2 } host && /(Remote|Local|Dynamic)Forward/ { port=$2; gsub(".*:", "", port); print host, port }' ~/.ssh/config | while read host port; do
    nc -z 127.0.0.1 "${port}" || ssh -fqN "${host}" > /dev/null 2>&1
done

Written out more legibly, the AWK script is

/^Host / { host = $2 }

host && /(Remote|Local|Dynamic)Forward/ {
    port=$2; gsub(".*:", "", port); print host, port
}

This stores the host whenever it sees a “Host” statement, then on lines encountered while a valid host is defined, and matching one of the accepted “Forward” statements, retrieves the target port, drops the part before a colon if any, and outputs the host and port:

localsocksdynamic 8080
localwebserverremote 8080
indirectaccesslocal 2222

This is then fed into a while read loop to check the port and run SSH.

| improve this answer | |
  • This is exactly what I needed. I figured this was possible but it's going to be a while before my awk-fu can approach this level. – Q23 Jun 7 '17 at 21:33
  • What would be even better is if the script simply parsed for (Remote|Local|Dynamic)Forward directives, then went backwards through the file until it hit the Host directive that makes up that block of ~/.ssh/config. Then, the Host directive containing local, remote, or dynamic wouldn't be necessary. – Q23 Jun 7 '17 at 21:39
  • This is extremely helpful for trying to understand the awk syntax. I knew it could do what I wanted in one shot! – Q23 Jun 8 '17 at 14:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.