Writing shell scripts which execute locally or remotely

Posted by Steve on Mon 12 Mar 2007 at 07:12

There are a lot of times when it is useful to have a single shell script run both upon the local host, and also upon remote hosts. Here we'll show a simple trick which allows you to accomplish this easily.

To execute shell scripts remotely the most obvious approach is to copy it there, with scp, and then use ssh to actually execute it. This is similar to running simple commands remotely using ssh directly:

skx@mine:~$ ssh yours uptime
 07:12:25 up 3 days, 18:15,  0 users,  load average: 0.00, 0.00, 0.08
skx@mine:~$

With that in mind the solution becomes:

As an example we'll look at a simple script which will report upon the uptime of the system it is executed upon:

Here is the script:

#!/bin/sh

#
#  Are we installing locally?  Or remotely?
#
if [ ! -z $1 ]; then

    #  Hostname
    host=$1

    #  Create a secure temporary file.
    file=`mktemp`

    #  Create a temporary file, and copy the contents of ourself into
    # it.  Making sure it has a shebang.
    echo "#!/bin/sh"                > "${file}"
    grep -A2000 '^#-=-MARKER-=' $0 >> "${file}"
    chmod 755 "${file}"

    #  Copy the file to the remote host, and invoke it
    scp "${file}" ${host}:
    ssh "${host}" ./`basename ${file}`

    #  Cleanup remotely and locally.
    ssh "${host}" /bin/rm `basename ${file}`
    rm ${file}

    #  All done - the rest of the script will occur remotely.
    exit
fi


## THE NEXT LINE IS IMPORTANT     - DO NOT EDIT.  DO NOT REMOVE.
#-=-MARKER-=-
## THE PREVIOUS LINE IS IMPORTANT - DO NOT EDIT.  DO NOT REMOVE.

uptime

Here you can see that the script detects whether to run remotely or not based upon the presence of a command line argument, so this is local execution:

skx@mine:~$ ./uptime.sh
 14:05:10 up  4:59,  4 users,  load average: 0.05, 0.05, 0.07

Whereas this is remote:

skx@mine:~$ ./uptime.sh cfmaster.my.flat
tmp.RRjRSx9137                                100%   98     0.1KB/s   00:00
 14:05:28 up 430 days, 20:02,  0 users,  load average: 9.72, 6.58, 4.26

Neat huh?

The key to this script is that it can separate out the "real" work of the script so that only the end of the script is copied to the remote host - the part after the argument processing. This is achieved with the following command:

grep -A2000 '^#-=-MARKER-=' $0

This uses the "-A" option of GNU grep to cause it to print out a number of line after the line beginning "#-=-MARKER-=" - this is the part of the script that actually reports on the system uptime, and this is the part you'd replace with your own code.

The relevant lines are then placed into a temporary file and copied to the host upon which it should execute them. (If you didn't have key-based authentication setup you'd be prompted for your password three times; the first time for the copy, the second time for the execution, and the final time to cleanup the file which was copied.)

Using a simple system like this you could easily write scripts that would preform tasks like installing CFEngine locally or remotely.


This article can be found online at the Debian Administration website at the following bookmarkable URL (along with associated comments):

This article is copyright 2007 Steve - please ask for permission to republish or translate.