Shell scripting tutorials – prefix commands

Prefix commands are a seriously useful concept in shell scripting. These are commands that you put before another command to modify its behaviour. The Bourne shell (sh) and Bash shells (bash) have several prefix commands built in, which I’ll cover briefly, but most of this tutorial is about how to write your own.

Built-in Prefix Commands

Lets say you have a script called backup. You can run this from the shell by simply typing backup or including this command in a script:

$ backup

However, this might hog your system resources. So you can lower the priority of the command by using the nice prefix command:

$ nice backup

This will run the backup command at a lower priority. What’s good about this is that you don’t need to modify your backup script to do this, the nice command can be used to lower the priority of any command.

Prefix commands can have options. The default nice command lowers the priority of its command by 10. However, the -n option can be used to modify that:

$ nice -n 20 backup

This will run at an even lower priority. Alternatively, you can raise the priority of a command, provided you have sufficient privileges to do so (on many Unixy platforms, you cannot raise the priority above 0, which is the default) by using a negative argument:

$ nice -n -10 backup

Other built-in prefix commands that I use regularly are:

  • time – measure the CPU time used by the command
  • env – modify the environment of the command

So, for example, I can set an environment variable needed by my backup script using the env prefix command:

$ env BACKUP_TARGET=$HOME backup

This will set the environment variable BACKUP_TARGET for the duration of the backup command only.

A prefix command returns the exit status of the command so that adding a prefix does not modify the behaviour of a script containing the command. For example nice backup returns the same exit status as backup. This also allows prefix commands to be chained. For example, nice time backup will both lower the priority and measure the CPU time, returning the exit status of the backup command.

Writing Your Own Prefix Commands

A prefix command is just a shell script which takes as its parameters some options followed by another command.

The task of the prefix command script is to:

  1. parse command-line options
  2. modify the environment
  3. run the command
  4. return the exit status of the command

As an example, I’ll demonstrate how to write a script that pauses for a specified time before running the command. I use this in logon scripts to start maintenance tasks some time after the business of logon has died down.

The objective is to write a command with the following syntax:

usage: sleep-before [ -h ] [ -w <wait> ] <command>

Where the -h options prints out help information and the -w option determines the wait time in seconds before the command is started.

Basic Shell Script

The start of any shell script is to create a file where the filename is the same as the command, in this case sleep-before. There is no extension on a shell script. This file should be created with execute privileges. The easiest way to do this is to create the file with touch (creates an empty file if there is none) and then change its permissions with chmod:

touch sleep-before
chmod +x sleep-before

Now edit the file and add the usual shebang starting line:

#!/bin/sh

I tend to specify /bin/sh as the shell where possible, because this is likely to be more portable between OSs than /bin/bash. However, bash has extra functionality and sometimes this just makes scripting much easier, so make your own choice there.

A preliminary stage that I always include when writing scripts is to write a help function that prints out help information either on request or on error. I always prefer to do this as a function so it can be called from anywhere:

function usage()
{
  cat >&2 <<-EOF
	usage: $0 [ -h ] [ -w <wait> ] <command>
	  a prefix operator that waits before executing <command>
	  <command> : the command to execute in the background
	  -h        : this help
	  -w <wait> : the delay in seconds to wait before (default 0)
	EOF
}

This has been implemented as a ‘here doc’, where the text is enclosed between two strings (in this case EOF but it can be anything), piped into the cat command which prints it to standard error.

Parse Command-Line Options

The first stage in the script is to perform command-line parsing. I do this using the getopts shell command:

error=0
wait=0
# process options
while getopts w:h option; do
  case $option in
    w) wait=$OPTARG ;;
    h) usage; exit 0 ;;
    ?) error=1 ;;
  esac
done
shift $((OPTIND-1))
# check for the existence of a script to run
if [ $# -eq 0 ]; then
  echo "$0: a command to run is required" >&2
  error=1
fi
# give up on error
if [ $error -gt 0 ]; then
  usage
  exit 2
fi

See the manual page for details of the getopts command. Basically, this parses the -h and -w options, rejects any other options and stops parsing when it gets to a non-option. The shift operation then discards the processed options, leaving just the command to be executed in the command-line.

The remainder of this code is error checking – there must be at least one argument making up the command and then the script exits if any errors were found in the command-line parsing phase. The exit status is 2 because this is a common convention for ‘incorrect usage’ errors.

Note that the default wait is 0, which is set right at the start of this sequence.

Wait Before Performing Command

The second stage of a prefix command is to modify the environment of the command, in this case by waiting before performing the command itself. This is done using the sleep command:

if [ $wait -gt 0 ]; then
  echo -n "sleeping for ${wait}s..."
  sleep $wait
  echo "done"
fi

The sleep is only performed if the wait option is positive.

Perform the Command

The third stage of the script is to perform the command itself. This is done by executing the remainder of the sleep-before command’s command line now that the options have been discarded:

"$@"

This is a standard bit of boilerplate that turns the command-line arguments ($@) into a command and quotes any arguments that need quoting, either because they contain spaces or other special characters.

Return the Exit Status

In shell scripts, the default exit status is the exit status of the last command in the script. So to meet the prefix command requirement of returning the command’s status, we don’t need to do anything at all, just end the script here.

However, often there is some clean-up script after the command has executed. In that case, the exit status of the command must be stored in a variable and then used in an explicit exit command later:

"$@"
status=$?
<clean-up commands>
exit $status

The $? variable contains the exit status of the last-executed command. It needs to be captured before any other commands are executed which would overwrite the value.

Leave a Reply