views:

4852

answers:

7

What is your favorite method to handle errors in BASH? The best example of handling errors in BASH I have found on the web was written by William Shotts, Jr at http://www.linuxcommand.org.

William Shotts, Jr suggests using the following function for error handling in BASH:

#!/bin/bash

# A slicker error handling routine

# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run.  You can get this
# value from the first item on the command line ($0).

# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php&gt;

PROGNAME=$(basename $0)

function error_exit
{

#   ----------------------------------------------------------------
#   Function for exit due to fatal program error
#    Accepts 1 argument:
#     string containing descriptive error message
#   ----------------------------------------------------------------


    echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
    exit 1
}

# Example call of the error_exit function.  Note the inclusion
# of the LINENO environment variable.  It contains the current
# line number.

echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."

Do you have a better error handling routine that you use in BASH scripts?

A: 

Not sure why you need to have a function for this? I just do

echo Error: something bad happened!
exit 1

Which is exactly the same as the example above, without the all the fanfare.

zigdon
You can save a line of error handling code by using a function. And you can make sure to do it right in one place (unlike your solution which doesn't use stderr for error output). And by using a function, you can do stuff like this: do_something || error_exit "do_something failed"
Troels Arvin
+1  A: 

I've used

die() {
        echo $1
        kill $$
}

before; i think because 'exit' was failing for me for some reason. The above defaults seem like a good idea, though.

pjz
+3  A: 

That's a fine solution. I just wanted to add

set -e

as a rudimentary error mechanism. It will immediately stop your script if a simple command fails. I think this should have been the default behavior: since such errors almost always signify something unexpected, it is not really 'sane' to keep executing the following commands.

Bruno De Fraine
+2  A: 

Another consideration is the exit code to return. Just "1" is pretty standard, although there are a handful of reserved exit codes that bash itself uses, and that same page argues that user-defined codes should be in the range 64-113 to conform to C/C++ standards.

You might also consider the bit vector approach that mount uses for its exit codes:

 0  success
 1  incorrect invocation or permissions
 2  system error (out of memory, cannot fork, no more loop devices)
 4  internal mount bug or missing nfs support in mount
 8  user interrupt
16  problems writing or locking /etc/mtab
32  mount failure
64  some mount succeeded

OR-ing the codes together allows your script to signal multiple simultaneous errors.

yukondude
+3  A: 

Use a trap!

TEMPFILES=( )
function cleanup() {
  rm -f "${TEMPFILES[@]}"
}
trap cleanup 0

function error() {
  local PARENT_LINENO="$1"
  local MESSAGE="$2"
  local CODE="${3:-1}"
  if [[ -n "$MESSAGE" ]] ; then
    echo "Error on or near line ${PARENT_LINENO}: ${MESSAGE}; exiting with status ${CODE}"
  else
    echo "Error on or near line ${PARENT_LINENO}; exiting with status ${CODE}"
  fi
  exit "${CODE}"
}
trap 'error ${LINENO}' ERR

...then, whenever you create a temporary file:

TEMP_FOO="$(mktemp -t foobar.XXXXXX)"
TEMPFILES+=( "$TEMP_FOO" )

and $TEMP_FOO will be deleted on exit, and the current line number will be printed. (set -e is always a good practice, and will likewise give you exit-on-error behavior).

You can either let the trap call error for you (in which case it uses the default exit code of 1 and no message) or call it yourself and provide explicit values; for instance:

error ${LINENO} "the foobar failed" 2

will exit with status 2, and give an explicit message.

Charles Duffy
+2  A: 

I prefer something really easy to call. So I use something that looks a little complicated, but is easy to use. I usually just copy-and-paste the code below into my scripts. An explanation follows the code.

#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
    echo
    echo "$@"
    exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM

#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'

I usually put a call to the cleanup function in side the error_exit function, but this varies from script to script so I left it out. The traps catch the common terminating signals and make sure everything gets cleaned up. The alias is what does the real magic. I like to check everything for failure. So in general I call programs in an "if !" type statement. By subtracting 1 from the line number the alias will tell me where the failure occurred. It is also dead simple to call, and pretty much idiot proof. Below is an example (just replace /bin/false with whatever you are going to call).

#This is an example useage, it will print out
#Error prog-name (@1): Who knew false is false.
if ! /bin/false ; then
    die "Who knew false is false."
fi
Michael Nooner
+1  A: 

An equivalent alternative to "set -e" is

set -o errexit

It makes the meaning of the flag somewhat clearer than just "-e".

Random addition: to temporarily disable the flag, and return to the default (of continuing execution regardless of exit codes), just use

set +e
$(DO_SOMETHING_DANGEROUS_HERE)
set -e

This precludes proper error handling mentioned in other responses, but is quick & effective (just like bash).

Ben Scholbrock