Overview
One of the most common problems with writing any kind of program is lack of proper error handling.
If a program does not sanity check its operations and data, it can sometimes proceed for many subsequent steps until finally either a bad (or empty) result is generated, or some called program that does sanity check its data notices and terminates execution. It is then challenging to backtrack to where the original, causative error occurred – assuming there are even log files available to examine!
Some languages (Python, Java, R) automatically detect certain types of errors (e.g. file not found) and, by default, stop program execution and report an execution stack trace that can be displayed to the user. While these stack traces are not usually meaningful on their own, they are better than nothing, and certainly better than allowing the program to blithely continue.
But built-in error runtime error checks are no help in sanity checking program data, because only the programmer knows what the data should look like! Enter user-defined error checking. Again, some languages make this less cumbersome:
- Python – assert statement
- Java – assert keyword
- R – stopifnot statement
These languages also have throw/catch exception handling constructs, which lets a program detect and deal with errors – if possible.
There are mechanisms in bash that provide some of this functionality (see set -e and set -x decsribed in https://www.turnkeylinux.org/blog/shell-error-handling and http://linuxcommand.org/lc3_wss0150.php, and trap-ing signals in http://linuxcommand.org/lc3_wss0150.php).
Here, though, we're going to "roll our own" error handling with a set of functions. Our goals are:
- To stop execution immediately when an error is detected
- Have the last line of the log file describe the fatal error (easy to tail)
- Make it easy to frequently check for errors of different sorts in our code
- Report the check being done (even when successful) in order to leave "bread crumbs" in the log file for troubleshooting.
The step_04.sh Script
Here's a step_04.sh script that builds on our step_03.sh work. Copy it from ~/workshop/step_04.sh, or copy and paste this text into a new file, ~/step_04.txt in your home directory. Make sure it is executable (chmod +x ~/step_04.txt).
The Parts
There are a number of error checking functions defined in step_04.sh – and many more that could be added of course – but this is a good start to get the ideas across.
err function
The main error function is just called err, and is called with some descriptive text elsewhere when an error is detected. It outputs that text with some surrounding decoration, and exits with a non-0 error code. If we do our error checking properly, and an error occurs, this will be the last line in the log file. Or the text "...exiting" can also be grep'd for in the log file to see if something bad happened.
# General function that exits after printing its text
# in a standard format which can be easily grep'd.
err() {
echo_se "** ERROR: $@ ...exiting"; exit 255;
}
check_res function
The check_res function checks the result/exit code passed in to it (usually our friend $?)
# Function to check result code of programs.
# Exits with a standard error message if code is non-zero.
# Otherwise displays a completion message.
# arg 1 - the return code (usually $?)
# arg 2 - text describing what ran
check_res() {
if [[ $1 == 0 ]]; then maybe_echo ".. check_res: $2 OK";
else err "check_res: $2 returned non-0 exit code $1"; fi
}
exercise 1
Test the check_res function in a parentheses sub-shell with one or two arguments.
file checking functions
Several file/directory checking functions are defined:
- check_dir
- check_file
- check_file_not_empty
# Function that checks if a directory exists and exits if not.
# arg 1 - the directory name
# arg 2 - text describing the directory (optional)
check_dir() {
if [[ ! -d "$1" ]]; then err "$2 Directory '$1' not found"
else maybe_echo ".. check_dir: $2 directory '$1' exists"; fi
}
# Function that checks if a file exists
# arg 1 - the file name
# arg 2 - text describing the file (optional)
check_file() {
if [[ ! -e "$1" ]]; then err "$2 File '$1' not found"
else maybe_echo ".. check_file: $2 file '$1' exists"; fi
}
# Function checks if a file exists & has non-0 length, else exits.
# arg 1 - the file name
# arg 2 - text describing the file (optional)
check_file_not_empty() {
if [[ ! -e "$1" ]]; then err "$2 File '$1' not found"
elif [[ ! -s "$1" ]]; then err "$2 File '$1' is empty"
else maybe_echo ".. check_file_not_empty: $2 file '$1' OK"; fi
}
exercise 2
Explore these file checking functions in the safety of a sub-shell.