Elegance of Reason

Tuesday, December 06, 2005

Portability Interlude

In the previous post, the syntax ${2:-1} was used in the implementation of die() in the usual sense of providing a default value of 1 if parameter $2 is null or unset.

According to The Single UNIX Specification, the above syntax is the standard-blessed way to obtain the intended effect; according to the Autobook, however, the notation ${2-1} would be more portable. Experimentation with a few sh-derived shells, shows that none chokes on either syntax; on the matter of portability it is always wise to acknowledge consolidated practice, so the implementation of die() is hereby changed accordingly.


warn() {
echo 1>&2 "$0: $1"
}

die() {
warn "$1"; exit ${2-1}
}

Saturday, December 03, 2005

Improving shell scripts

Although there is no reason to exempt shell scripts from good programming practices, many scripts showing signs of such neglect can readily be found over the Internet. This is sad, because it brings shell programming down to the level of Visual Basic, and because it can be easily avoided.

Consider a common task: upon encountering a condition which does not allow script execution to continue, show a diagnostic and exit. Most scripts do that, it's how they do it that could use some improvement. Consider a first attempt:


warn() {
echo 1>&2 "$0: $1"
}

die() {
warn "$1"; exit ${2:-1}
}


Here the warn() function anticipates a variation of the initial use case: the script encountered a condition which is supposedly of some interest to the user, but does not prevent the script from proceeding with execution. The die() function, also borrowed from Perl, just puts it to good use and exits.

The message is printed on standard error: this is an important and too often overlooked behavior, which makes parsing standard output easier and therefore the script more useful in pipelines.

Furthermore, according to Unix conventions, the message begins with the command name $0 followed by a semicolon, a space, and the diagnostic proper; this helps when a script is invoked by another script, to sort out who produced the diagnostic.

Last, die() exits the script with a non-zero return code, again according to Unix conventions. The actual exit code can be left unspecified, for the convenience of the caller in code such as:


tar --version >/dev/null 2>&1 || die "cannot find tar(1)"
tar -c -f /dev/nst0 /opt || warn "Backup failed"


The above code is not completely satisfying, but at least offers a starting point for putting some self-respect in shell scripts. The Perl module Pod::Usage provides some directions which can be borrowed to improve it, and there is also an instance of poor quoting, which will be fixed in a forthcoming post.