Unix shell scripting


This document attempts to be a bare-bones outline for how to write scripts under Unix, and how to use crontab to run scripts at regular intervals.

A much more complete reference can be found at http://tldp.org/LDP/abs/html.

I will assume that you are using bash, or some other similar shell like zsh or ksh. Plain sh won't do some of the things here, and csh is just too different.

Executables and your PATH


A bash script is just a text file that contains unix commands. If the file is named foo, then you can run it with bash -c foo, but it is much more common to make the file executable, with chmod +x foo, and then put it somewhere in your PATH. Once it is executable you can run it with ./foo without worrying about your PATH, but it is common to create a directory $HOME/bin and then append this to your PATH with PATH=$PATH:$HOME/bin. This means that just typing foo will now run your script, regardless of whether you've changed directory to somewhere else.

Most people put the PATH assignment above into their bash startup file, .bashrc. Note that if you want your script to run in an environment where .bashrc isn't seen, such as crontab, you should either set the PATH or use a fully-qualified name ($HOME/bin/foo).

Redirection

You can direct the output of a command into a file with the > operator. If you type ls > ls.out, then the output of the ls command goes to the file ls.out; this works for built-in commands like ls and also your own commands like foo.

If you want to append to a log file, use >> instead. The following would append three lines of output to the file log.out:
    date >> log.out
    uptime >> log.out
    snmpget -v 1 -c public snmphost system.1.0   >> log.out      # assume this generates one line of output
You can also create a single script file foo containing
    date
    uptime
    snmpget ....
and then redirect the whole thing with foo >> log.out. For complex logging, this is often simpler.

The > and >> operators redirect the standard output. If you also want to redirect the standard error, to get your error messages, you can do it with 2>, but the most common idiom is 2>&1, which means to redirect the standard error (file descriptor 2) to wherever the standard output is currently heading. Note that you must redirect stdout before invoking 2>&1.

Shell Variables

The shell can use string variables for both programming and for simple bookkeeping. The variables HOME and PATH were used above; to view these, the env command will print them all or you can use echo $HOME, etc. Note that to access the value of a variable, you need to prepend a $.

Getting command output into a shell variable

The echo command takes what is on the command line (including variables) and prints it out (that is, sends it to stdout). If you want to go the other way, capturing the output of a command into a shell variable, use:
     LS_OUT=$(ls -l myfile.stuff)
It is simplest if you do this with commands that you know will produce only a single line of output.

Parsing command output lines

Often command output is intended to be human-readable and thus has lots of stuff in it that you don't want if you're feeding it back into another script. For example, the ps command will list all sorts of information about a process, but perhaps all you want is the process Id, which is field 1 (after a leading space). If the delimitering is based on single separation characters (eg TAB or : or ;), or is based on fixed column counts, consider the cut command. For free-form output that forms distinct tokens, awk works well. To get the first word of output, use awk '{ print $1}' (note the quotes!!). $2 will give you the second word.

Looping and Conditionals

Shell conditionals are done with the if command; looping with the while and for commands (there is also case, and a few other forms). For looping, if you want a loop variable to increment, consider using the expr command that evaluates the rest of the command line as an arithmetical expression. Thus, if you are using the shell variable INDEX in your loop, currently 15, then
    expr $INDEX + 1       # note the spaces
shell-evaluates to
    expr 15 + 1
which then would print 16 to its stdout. Typically you want this back into the variable INDEX, which would be achieved with
    INDEX=$(expr $INDEX + 1)

Parameters

Shell commands have parameters. The first parameter is $1, then $2, etc; $0 is the name of the command. $* is the entire command line and $# is the number of parameters. For the hard-core, there is also shift.

crontab

The cron daemon runs commands at designated intervals; the file of your personal cron entries is crontab. Nowadays there is usually also a command called crontab that edits this file: crontab -e.

A crontab line consists of five date/time fields and a command, separated by spaces. We'll only consider the first two of the date/time fields here, for minutes and hours; a * in the other fields means to run on any date when the specified minute and hour is reached. If you want to run a command every 4 hours, the following crontab entry will do that:
    0  0,4,8,12,16,20  *  *  *    $HOME/bin/foo
If you want to run a command every fifteen minutes, the following will do:
    0,15,30,45  *  *  *  *    $HOME/bin/theCommand