AllGoodBits.org

Shell Scripting Tips

In no particular order, here is a collection of tips to remember to help make shell scripts better. Some of these will apply more generally to other languages, especially other shells. I call them tips, because my research has been less than rigourous, particularly with regard to portability between shells/platforms.

Execution

Check that your script has valid syntax without executing it:

/bin/sh -n script.sh

Watch every command in your script as it is executed:

/bin/sh -x script.sh

$(command) is a better way to execute command than using backticks, if nothing else because it handles quoting and nesting.

Often, particularly on modern linux systems, executing with /bin/sh will
get Bourne(-like?) behaviour from Bash (since /bin/sh itself is merely
either a symlink to Bash or indeed the Bash executable itself, in contrast to using /bin/bash, which will get Bash semantics.

Quoting, Variables and Arguments

Always use the full syntax for referencing a variable, it's just safer:

${VAR}

Quoting the variable reference means it won't be broken by a space:

"${VAR}"

Arguments

"$@"

all the arguments.

$#

the number of arguments.

Default values for variables

Use set -u to treat unset variables as an error.

Defaults are often useful. If $default is defined and not empty, set var to its value, otherwise set it to SOMEVALUE:

var=${default-SOMEVALUE}

The above is Bourne compliant, POSIXish shells like ksh and bash allow the following equivalent syntax:

The : in the getopts options definition is magic, it means that the preceding option takes a value.

$OPTARG is magic, it's how getopts populates the option value into the variable that should hold it.

With the caveat that getopts in sh(1) only handles short options, use GNU getopt or a bigger scripting language if you need more than this, this handles the common use case.

set -x is a good start for verbosity, more verbose logging should probably use a log() function like die() below.

The final shift means that non-option arguments are now available in $@/$1,$2,..$N

Comparisons

-eq, -ne, gt, etc.

integer comparisons

=, !=, <, etc.

string comparisons

(())

arithmetic expressions, be careful with exit status, they are opposite
from [ ... ] tests.

If you are using Bash, you probably want to prefer [[ ]] (double-bracket
test operator) over [ ] (POSIX test operator, equivalent to
/usb/bin/test, because you can use &&, ||, <, > and pattern matching inside [[
]]. If you want filename expansion or word splitting, you must use the
single-bracket test operator.

Reading input from terminal

Read from the terminal, putting the content into a variable, INPUT:

read -p "Input please: " INPUT
echo $INPUT

The -s flag turns off echoing to the terminal in case you want to ask for a password:

read -s -p "Password: " pw

Colouring output with tput(1)

Although in my opinion, colour output is often overused or badly used, it's sometimes helpful. Here's an example:

Signal Handling with trap

Sometimes you'll want perform cleanup in the event of receiving SIGINT or SIGTERM (or the pseudo-signal EXIT which is triggered by the built-in called or "exit" or the failure of any command while set -e is active.

Portability

most items here are likely to be sh(1) compatible (i.e. original Bourne shell).

some techniques will require bash(1) (GNU Bourne-Again shell).

I have made little attempt to note which versions of the shells support any particular feature or technique, although it is my expectation that all examples will likely work with anything even vaguely modern such GNU Bash version 3 or higher.

I have made no attempt whatsoever to try to account for different platforms (Linux distributions, BSD variants or anything more exotic), my usual environments are common linux distributions such as RHEL-derivatives, SuSE and FreeBSD.

sh (or Shell Command Language) is a programming language described by the POSIX standards, also known as the IEEE Std 1003 family. It has many implementations (ksh88, dash, ...). For example, look at The Open Group's Base Specifications Issue 6 otherwise known as IEEE 1003.1 2004 Edition.

bash can also be considered an implementation of sh. Bash started as sh-compatible, but there are many differences. It supports a POSIX mode--posix switch and tries to use POSIX compliant with either the POSIXLY_COMPLIANT environment variable or if it is invoked as sh.