escaping complicated command line arguments in bash and ssh

Viewed 123

I am working on a bash script to retrieve uptime of a list of servers. It just reads from a file and executes a remote command on the server. Some servers may have been up for a long time while some may have just been rebooted. I was using something very simple:

uptime | awk '{print \$2,\$3,\$4}'

This works if the server has been up for at least a day, but if it's been up only for a few hours the output from uptime doesn't include the work days, and just uses an hour:minute format.

I found something that I quite like, but can't seem to escape it properly in the ssh command, let alone trying to pass it in the bash script:

uptime | awk -F'( |,|:)+' '{d=h=m=0; if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes."}'

I have tried several things, but this to me should work and can't seem to figure out what I am missing:

ssh user@IP "uptime | awk -F'( |,|:)+' '{d=h=m=0; if (\$7==\"min\") m=\$6; else {if (\$7\~\/^day\/) {d=\$6;h=\$8;m=\$9} else {h=\$6;m=\$7}}} {print d+0,\"days,\",h+0,\"hours,\",m+0,\"minutes.\"}'"

The output I get is:

awk: {d=h=m=0; if ($7=="min") m=$6; else {if ($7\~\/^day\/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes."}
awk:                                            ^ backslash not last character on line

In the end I am trying to put this in a bash script that is something like this:

while IFS= read -r server; do
    ssh -tt user@$server  "uptime command" < /dev/null >> /home/user/uptime
done < "$file"
2 Answers

Just send the commands to the standard input of ssh:

cat <<'EOF' | ssh user@machine
uptime | awk -F'( |,|:)+' '{d=h=m=0; if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes."}'
EOF

It looks to me like you're escaping too much:

ssh user@IP "uptime | awk -F'( |,|:)+' '{d=h=m=0; if (\$7==\"min\") m=\$6; else {if (\$7~/^day/) {d=\$6;h=\$8;m=\$9} else {h=\$6;m=\$7}}} {print d+0,\"days,\",h+0,\"hours,\",m+0,\"minutes.\"}'"

I get:

6 days, 11 hours, 21 minutes.

Update:

Since the same command is meant to be sent to more than one different remote host, I recommend saving it to a variable for easy reuse.

command="uptime | awk -F'( |,|:)+' '{d=h=m=0; if (\$7==\"min\") m=\$6; else {if (\$7~/^day/) {d=\$6;h=\$8;m=\$9} else {h=\$6;m=\$7}}} {print d+0,\"days,\",h+0,\"hours,\",m+0,\"minutes.\"}'"
ssh user@IP1 "$command"
ssh user@IP2 "$command"

Test run:

$ bash script.sh
0 days, 2 hours, 26 minutes.
7 days, 4 hours, 18 minutes.

Wait, there's more:

I often need to send multiple commands to the remote hosts: for this I find the quoted heredoc syntax, suggested by choroba in his answer, to be most handy.

command=$(cat <<'EOF'
hostname
uptime | awk -F'( |,|:)+' '{d=h=m=0; if ($7=="min") m=$6; else {if ($7~/^day/) {d=$6;h=$8;m=$9} else {h=$6;m=$7}}} {print d+0,"days,",h+0,"hours,",m+0,"minutes."}'
EOF
)
ssh user@IP1 "$command"
ssh user@IP2 "$command"

This is the output:

$ bash script.sh
server1
0 days, 2 hours, 26 minutes.
server2
7 days, 4 hours, 18 minutes.
Related