bash errors
Abstract
Use
set -euo pipefail
in bash scripts for safer execution.
Reviews
Mandatory flags to use in bash scripts. They saved me many times!
Often times developers go about writing bash scripts the same as writing code in a higher-level language. This is a big mistake as higher-level languages offer safeguards that are not present in bash scripts by default. For example, a Ruby script will throw an error when trying to read from an uninitialized variable, whereas a bash script won’t. In this article, we’ll look at how we can improve on this.
The bash shell comes with several builtin commands for modifying the behavior of the shell itself. We are particularly interested in the set builtin, as this command has several options that will help us write safer scripts. I hope to convince you that it’s a really good idea to add set -euxo pipefail
to the beginning of all your future bash scripts.
set -e
The -e
option will cause a bash script to exit immediately when a command fails. This is generally a vast improvement upon the default behavior where the script just ignores the failing command and continues with the next line. This option is also smart enough to not react on failing commands that are part of conditional statements. Moreover, you can append a command with || true
for those rare cases where you don’t want a failing command to trigger an immediate exit.
Before
After
Any command returning a non-zero exit code will cause an immediate exit
Preventing an immediate exit
Failing commands in a conditional statement will not cause an immediate exit
That’s all for set -e
. However, set -e
by itself is far from enough. We can further improve upon the behavior created by set -e
by combining it with set -o pipefail
. Let’s have a look at that next.
set -o pipefail
The bash shell normally only looks at the exit code of the last command of a pipeline. This behavior is not ideal as it causes the -e
option to only be able to act on the exit code of a pipeline’s last command. This is where -o pipefail
comes in. This particular option sets the exit code of a pipeline to that of the rightmost command to exit with a non-zero status, or to zero if all commands of the pipeline exit successfully.
Before
After
This section hopefully made it clear that -o pipefail
provides an important improvement upon just using -e
by itself. However, as we shall see in the next section, we can still do more to make our scripts behave like higher-level languages.
set -u
This option causes the bash shell to treat unset variables as an error and exit immediately. Unset variables are a common cause of bugs in shell scripts, so having unset variables cause an immediate exit is often highly desirable behavior.
Before
After
Dealing with ${a:-b} variable assignments
Sometimes you’ll want to use a ${a:-b} variable assignment to ensure a variable is assigned a default value of b
when a
is either empty or undefined. The -u
option is smart enough to not cause an immediate exit in such a scenario.
Using conditional statements that check if variables are set
Sometimes you want your script to not immediately exit when an unset variable is encountered. A common example is checking for a variable’s existence inside an if
statement.
This section has brought us a lot closer to making our bash shell behave like higher-level languages. While -euo pipefail
is great for the early detection of all kinds of problems, sometimes it won’t be enough. This is why in the next section we’ll look at an option that will help us figure out those really tricky bugs that you encounter every once in a while.
set -x
The -x
option causes bash to print each command before executing it. This can be a great help when trying to debug a bash script failure. Note that arguments get expanded before a command gets printed, which will cause our logs to contain the actual argument values that were present at the time of execution!
That’s it for the -x
option. It’s pretty straightforward, but can be a great help for debugging. Next up, we’ll look at an option I had never heard of before that was suggested by a reader of this blog.
Reader suggestion: set -E
Traps are pieces of code that fire when a bash script catches certain signals. Aside from the usual signals (e.g. SIGINT
, SIGTERM
, …), traps can also be used to catch special bash signals like EXIT
, DEBUG
, RETURN
, and ERR
. However, reader Kevin Gibbs pointed out that using -e
without -E
will cause an ERR
trap to not fire in certain scenarios.
Before
After
The documentation states that -E
needs to be set if we want the ERR
trap to be inherited by shell functions, command substitutions, and commands that are executed in a subshell environment. The ERR
trap is normally not inherited in such cases.
Conclusion
I hope this post showed you why using set -euxo pipefail
(or set -Eeuxo pipefail
) is such a good idea. If you have any other options you want to suggest, then please let me know and I’ll be happy to add them to this list.