By Evan Sangaline | September 11, 2017
It’s a common issue that scripts written and tested on GNU/Linux don’t run correctly on macOS–or vice versa–because of differences between the GNU and BSD versions of the core utils. Error messages can get drowned in the script output, making it far from obvious that something isn’t executing correctly. There are a couple of easy fixes to avoid problems like this, but they rely on some bash features that you may not be familiar with if you don’t do a ton of scripting. I’ll summarize my two approaches here and hopefully they’re of some use to you if you’re looking for a how-to guide for this specific problem.
Exit When Any Command Fails
This can actually be done with a single line using the set
builtin command with the -e
option.
# exit when any command fails
set -e
Putting this at the top of a bash script will cause the script to exit if any commands return a non-zero exit code.
We can get a little fancier if we use DEBUG
and EXIT
traps to execute custom commands before each line of the script is run and before the script exits, respectively.
Adding the following lines will allow us to print out a nice error message including the previously run command and its exit code.
# exit when any command fails
set -e
# keep track of the last executed command
trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
# echo an error message before exiting
trap 'echo "\"${last_command}\" command filed with exit code $?."' EXIT
For example, if we run ls --fake-option
then we’ll see an informative error message as follows.
ls: unrecognized option '--fake-option'
Try 'ls --help' for more information.
"ls --fake-option" command filed with exit code 2.
Exit Only When Specific Commands Fail
The global solution is often fine, but sometimes you only care if certain commands fail. We can handle this situation by defining a function that needs to be explicitly invoked to check the status code and exit if necessary.
exit_on_error() {
exit_code=$1
last_command=${@:2}
if [ $exit_code -ne 0 ]; then
>&2 echo "\"${last_command}\" command failed with exit code ${exit_code}."
exit $exit_code
fi
}
# enable !! command completion
set -o history -o histexpand
That bottom line isn’t strictly necessary, but it allows us to use !!
and have it expand to the last command executed.
For example, we can check explicitly for an error like this
ls --fake-option
exit_on_error $? !!
will pass the exit code of the previous command as the first argument to exit_on_error()
and then !!
will expand to ls --fake-option
as the second and third arguments.
The second and third arguments–plus any further arguments if they were there–are then recombined by slicing ${@:2}
.
This approach will print the same error message as the one in the previous section, but will only check the commands that are explicitly followed by exit_on_error
calls.
Conclusion
Hopefully you found one of these two methods helpful! If you’re ever struggling with any devops or infrastructure issues then please reach out about our consulting services in these areas.
Suggested Articles
If you enjoyed this article, then you might also enjoy these related ones.
Breaking Out of the Chrome/WebExtension Sandbox
A short guide to breaking out of the WebExtension content script sandbox.
Building a YouTube MP3 Downloader with Exodus, FFmpeg, and AWS Lambda
A short guide to building a practical YouTube MP3 downloader bookmarklet using Amazon Lambda.
Running FFmpeg on AWS Lambda for 1.9% the cost of AWS Elastic Transcoder
A guide to building a transcoder using Exodus, FFmpeg, and AWS Lambda.
Comments