Allowing for Errors in Bash When You Have set -e Defined
Using set -e is great for dealing with errors in Bash, but sometimes you want to gracefully handle an error instead of exit immediately.
It’s generally a good idea to use set -e
in your Bash scripts because when you
do, Bash will immediately halt your script when a command you run results in an
error, or more specifically exits with an exit code that’s not 0.
I’ve written about
trapping errors with Bash
when using set -e
which explains how set -e
works, so I recommend skimming
that for a quick run if you’re not sure what set -e
is.
# A Real World Use Case
Lately I’ve been working hard putting together my next course and one focus point of that course is deploying web applications.
While creating an app deployment tool (with Bash) I wanted you to be able to choose whether or not a database migration gets ran during the deploy.
This tool is something I’ve been working on for a while because I have been using it for my own projects and client work for quite some time now.
But when it comes to releasing it in a course, a certain level of polish needs to happen so I’ve been doing everything I can to make it as robust as possible.
I’m doing everything in my power to prevent you from being able to initiate a deploy with invalid parameters, such as a wrong service or container name, or in this case, a database migration command that’s invalid.
Naturally I’m using set -e
at the top of my script, and if you enter in an
invalid database migration command the entire script comes to a halt, which is
expected.
However, that’s not a very friendly user experience. What I really want to happen is to allow the command to fail, and then take a custom action depending on whether or not the command worked.
# Toggling set -e On Demand
Lucky for us, Bash is awesome, and there’s a way to easily allow for what we want.
An example of disabling set -e
to gracefully handle errors:
# Override the `set -e` defined at the top of the file.
set +e
# Run your command that may or may not work.
db_migrate_command
# Store the exit code of the previously ran command.
migrate_rc="${?}"
# Turn `set -e` back on so future errors halt the script.
set -e
# If the return code is not equal to zero then the command had an error.
if [ "${migrate_rc}" -ne 0 ]; then
# Write an informative error message and halt the script with an `exit 1`, or
# perhaps let it continue onwards, depending on what you're doing.
fi
# Everything is all good, do whatever you planned to do.
It’s worth mentioning that it’s important to store the rc (return code) of
the command inside of the set +e
and set -e
block because otherwise you’ll
capture the rc of running set -e
itself which is always going to return 0
.
That was a fun one to debug. :D
Using this pattern lets you keep set -e
on almost all the time and situationally
turn it off when you anticipate a command may fail. Victory!
Have you used this pattern before? Let me know below.