Running scripts on remote machines May 30, 2015 Written by @igor_sarcevic on

Several days ago a colleague and I wanted to check whether our caching system contains all the files that we expect it to contain. We started by writing a bash command that would list and compare the available files. It had the following structure:

cd somewhere && untar archive && diff < ( ls -lah directory ) < ( ls -lah other_directory )

We run the above command through an ssh connection from our local machine. In other words we did something along these lines:

ssh cacher@cache-server "cd somewhere && untar archive && diff <(ls -lah directory) <(ls -lah other_directory)"

As you can see the above command is pretty long even in this form. But it wasn’t sufficient. We added a couple of sed , grep , awk , tail , while commands just to make the output more human friendly. The result was a 4 lines long beast command that we tried to keep in a one long readline session. Imagine something like this:

ssh cacher@cache-server "cd somewhere && untar archive && ls directory | while read directory; do && diff echo $directory ; <(ls -lah directory | grep " * . * \1 " | sed " s/ \. spec//g " | tail) <(ls -lah other_directory | grep " * . * \1 " | sed " s/ \. spec//g " | tail) && echo " No errors " || echo " Error!!! "; done"

To make the command even more horrible, we run it on several remote machines with a nifty for loop:

for server in " ${ servers [@] } " ; { ssh cacher@ $server "cd somewhere && untar archive && ls directory | while read directory; do && diff echo $directory ; <(ls -lah directory | grep " * . * \1 " | sed " s/ \. spec//g " | tail) <(ls -lah other_directory | grep " * . * \1 " | sed " s/ \. spec//g " | tail) && echo " No errors " || echo " Error!!! "; done" }

Hunting down syntax errors was tedious, every time moving around with the left and right arrow… It was the time to put the command into a shell script!

The script

When you put the above command in a shell script, it becomes nice and tidy:

cd somewhere untar archive ls directory | while read directory ; do echo $directory ; original_list = $( ls -lah $directory | grep "*.* \1 " | sed "s/ \. spec//g" | tail ) new_list = $( ls -lah other_directory | grep "*.* \1 " | sed "s/ \. spec//g" | tail ) if diff < ( echo original_list ) < ( echo new_list ) ; then echo "No errors" else echo "Error!!!" ; fi done

But, how can we run the script on a remote machine?

The srtaight forward solution is to scp it to the remote machine and then execute the script with an ssh commmand:

for server in " ${ servers [@] } " ; { scp script.sh $server :/tmp/script.sh && ssh cacher@ $server "bash /tmp/script.sh && rm /tmp/script.sh" }

But, this still looks horible… Let’s look at an alternative…

Turns out you can push a file into the ssh command and ssh will redirect it to a remote command:

ssh cacher@cache-server "bash -s" < script.sh

With this we remove the need for a temp file, and the above command becomes:

for s in " ${ servers [@] } " ; { ssh cacher@ $s "bash -s" < script.sh }

This form looks acceptable.

Tweet