Exit codes are a number between 0 and 255, which is returned by any Unix command when it returns
control to its parent process.
Other numbers can be used, but these are treated modulo 256, so exit -10
is
equivalent to exit 246
, and exit 257
is equivalent to exit 1
.
These can be used within a shell script to change the flow of execution depending on the success or failure of commands executed. This was briefly introduced in Variables - Part II. Here we shall look in more detail in the available interpretations of exit codes.
Success is traditionally represented with exit 0
; failure is normally
indicated with a non-zero exit-code. This value can indicate different reasons for failure.
For example, GNU grep
returns 0
on success, 1
if no
matches were found, and 2
for other errors (syntax errors, non-existent input
files, etc).
We shall look at three different methods for checking error status, and discuss the pros and cons of each approach.
Firstly, the simple approach:
#!/bin/sh # First attempt at checking return codes USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` if [ "$?" -ne "0" ]; then echo "Sorry, cannot find user ${1} in /etc/passwd" exit 1 fi NAME=`grep "^${1}:" /etc/passwd|cut -d":" -f5` HOMEDIR=`grep "^${1}:" /etc/passwd|cut -d":" -f6` echo "USERNAME: $USERNAME" echo "NAME: $NAME" echo "HOMEDIR: $HOMEDIR"
/etc/passwd
.
However, if you enter an invalid code, it does not do what you might at first
expect - it keeps running, and just shows:
USERNAME: NAME: HOMEDIR:Why is this? As mentioned, the
$?
variable is set to the return code
of the last executed command. In this case, that is cut
. cut
had no problems which it feels like reporting - as far as I can tell from testing it, and
reading the documentation, cut
returns zero whatever happens! It was fed an
empty string, and did its job - returned the first field of its input, which just happened
to be the empty string.grep
will report it, not cut
.
Therefore, we have to test grep
's return code, not cut
's.
#!/bin/sh # Second attempt at checking return codes grep "^${1}:" /etc/passwd > /dev/null 2>&1 if [ "$?" -ne "0" ]; then echo "Sorry, cannot find user ${1} in /etc/passwd" exit 1 fi USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` NAME=`grep "^${1}:" /etc/passwd|cut -d":" -f5` HOMEDIR=`grep "^${1}:" /etc/passwd|cut -d":" -f6` echo "USERNAME: $USERNAME" echo "NAME: $NAME" echo "HOMEDIR: $HOMEDIR"
As a second approach, we can tidy this somewhat by putting the test into a separate function, instead of littering the code with lots of 4-line tests:
#!/bin/sh # A Tidier approach check_errs() { # Function. Parameter 1 is the return code # Para. 2 is text to display on failure. if [ "${1}" -ne "0" ]; then echo "ERROR # ${1} : ${2}" # as a bonus, make our script exit with the right error code. exit ${1} fi } ### main script starts here ### grep "^${1}:" /etc/passwd > /dev/null 2>&1 check_errs $? "User ${1} not found in /etc/passwd" USERNAME=`grep "^${1}:" /etc/passwd|cut -d":" -f1` check_errs $? "Cut returned an error" echo "USERNAME: $USERNAME" check_errs $? "echo returned an error - very strange!"
die
command in Perl.As a third approach, we shall look at a simpler and cruder method. I tend to use this for building Linux kernels - simple automations which, if they go well, should just get on with it, but when things go wrong, tend to require the operator to do something intelligent (ie, that which a script cannot do!):
#!/bin/sh cd /usr/src/linux && \ make dep && make bzImage && make modules && make modules_install && \ cp arch/i386/boot/bzImage /boot/my-new-kernel && cp System.map /boot && \ echo "Your new kernel awaits, m'lord."This script runs through the various tasks involved in building a Linux kernel (which can take quite a while), and uses the
&&
operator to check for success. To do this with if
would involve:
#!/bin/sh cd /usr/src/linux if [ "$?" -eq "0" ]; then make dep if [ "$?" -eq "0" ]; then make bzImage if [ "$?" -eq "0" ]; then make modules if [ "$?" -eq "0" ]; then make modules_install if [ "$?" -eq "0" ]; then cp arch/i386/boot/bzImage /boot/my-new-kernel if [ "$?" -eq "0" ]; then cp System.map /boot/ if [ "$?" -eq "0" ]; then echo "Your new kernel awaits, m'lord." fi fi fi fi fi fi fi fi
The &&
and ||
operators are the shell's equivalent of AND and OR
tests. These can be thrown together as above, or:
#!/bin/sh cp /foo /bar && echo Success || echo Failed
This code will either echo
Success
or
Failed
depending on whether or not the cp
command was successful. Look carefully at this;
the construct is
command && command-to-execute-on-success || command-to-execute-on-failure
Only one command can be in each part.
This method is handy for simple success / fail scenarios, but if you want to
check on the status of the echo
commands themselves, it is easy to quickly
become confused about which &&
and ||
applies to which
command. It is also very difficult to maintain. Therefore this construct is only
recommended for simple sequencing of commands.
In earlier versions, I had suggested that you can use a subshell to execute multiple commands depending on whether the cp
command succeeded or failed:
cp /foo /bar && ( echo Success ; echo Success part II; ) || ( echo Failed ; echo Failed part II )
But in fact, Marcel found that this does not work properly. The syntax for a subshell is:
( command1 ; command2; command3 )
The return code of the subshell is the return code of the final command (command3
in this example). That return code will affect the overall command. So the output of this script:
cp /foo /bar && ( echo Success ; echo Success part II; /bin/false ) || ( echo Failed ; echo Failed part II )
Is that it runs the Success part (because cp
succeeded, and then - because /bin/false
returns failure, it also executes the Failure part:
Success Success part II Failed Failed part II
So if you need to execute multiple commands as a result of the status of some other condition, it is better (and much clearer) to use the standard if
, then
, else
syntax.
My Shell Scripting books, available in Paperback and eBook formats. This tutorial is more of a general introduction to Shell Scripting, the longer Shell Scripting: Expert Recipes for Linux, Bash and more book covers every aspect of Bash in detail.
![]() Shell Scripting Tutorial is this tutorial, in 88-page Paperback and eBook formats. Convenient to read on the go, and in paperback format good to keep by your desk as an ever-present companion. Also available in PDF form from Gumroad:Get this tutorial as a PDF | ![]() Shell Scripting: Expert Recipes for Linux, Bash and more is my 564-page book on Shell Scripting. The first half covers all of the features of the shell in every detail; the second half has real-world shell scripts, organised by topic, along with detailed discussion of each script. |