Writing a Shell Script that executes Locally OR Remotely
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:
- Write a simple shell script which will be useful.
- Determine whether it should run remotely, and if so:
- Copy itself there.
- Execute itself there.
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.