Sometimes a programming language has a "strict mode" to restrict unsafe constructs. E.g., Perl has use strict
, Javascript has "use strict"
, and Visual Basic has Option Strict
. But what about bash? Well, bash doesn't have a strict mode as such, but it does have an unofficial strict mode:
set -euo pipefail
set -e
- Setting the
-e
, a.k.a.,errexit
, option, causes the script to exit immediately when an unhandled error occurs, instead of just continuing on. set -u
- Setting the
-u
option causes the script to treat references to unset variables as errors. E.g., if you misspell$MASTER_LINKS
as$MASTERLINKS
, which is unset, this will be treated as an error. set -o pipefail
- Setting the
pipefail
option causes the shell to treat an error in any command of a multi-stage pipeline to be an error in the pipeline as a whole. This is in contrast to the surprising default behavior of only considering the exit status of the last command in the pipeline.
Ways to set and unset this option:
set -e # Set the errexit option
set -o errexit # Equivalent
shopt -s -o errexit # Equivalent (TMTOWTDI FTW!)
set +e # Unset the errexit option
set +o errexit # Equivalent
shopt -u -o errexit # Equivalent
When set, the errexit
option causes the script to exit immediately when an unhandled error occurs. See the bash reference manual for details and exceptions:
https://www.gnu.org/software/bash/manual/bash.html#index-set
Example:
# remove the temporary files
cd "$JOB_HOME/job001/tmp"
rm *
If the cd
command failed, you wouldn't want to proceed with the execution of the rm
command. The errexit
option helps protect against such blunders.
Without errexit
, you'd need to be vigilant in checking the exit status or each command:
die() {
echo "$@" >&2
exit 1
}
...
# remove the temporary files
cd "$JOB_HOME/job001/tmp" || die "Couldn't cd into $JOB_HOME/job001/tmp"
rm *
- http://mywiki.wooledge.org/BashFAQ/105
- https://lists.gnu.org/archive/html/bug-bash/2012-12/msg00093.html
It is true that the use of set -e
involves some adjustments to your shell scripting style, but the safety it affords is well worth it.
Setting the -u
option causes the script to treat references to unset variables as errors.
Consider this example:
# Create or update the hardlink
ln -f proc.mk "$MASTERLINKS"/
In this example, we accidentally mispelled the $MASTER_LINKS
environment variable as $MASTERLINKS
, which is unset.
Without the -u
option set, the script will attempt to create the hard link at the root of the filesystem, /
.
With the -u
option, the script's reference to the unset variable $MASTERLINKS
will be treated as an error, and no attempt to create the hard link at the wrong location will be made.
By default, bash has the POSIX-mandated behavior of only considering the last command in a pipeline when determining the exit status of the pipeline as a whole. Setting the pipefail
option will cause the script to have the less surprising behavior of considering an error in any stage of the pipeline to be an error of the pipeline as a whole.
Consider this example:
$ cat myscript.sh
#!/bin/bash
false | echo 'hi'
echo "$?"
set -o pipefail
false | echo 'hi'
echo "$?"
$ ./myscript.sh
hi
0
hi
1
You see that when the pipefail
option is set, the non-zero exit status of the first stage of the pipeline causes the pipeline as a whole to have that same non-zero exit status. Without the pipefail option set, the behavior is surprising: the pipeline as a whole has a zero exit status; the error is silently ignored!
- Use the Unofficial Bash Strict Mode (Unless You Looove Debugging)
http://redsymbol.net/articles/unofficial-bash-strict-mode/
but without the stuff about changing IFS
.
Discussion of that blog post: https://news.ycombinator.com/item?id=8054440
- ShellCheck
https://www.shellcheck.net
ShellCheck helps find unsafe constructs in your shell script.
I made a single script from this information that can be used to have a coherent error handling, see https://gist.github.com/goulashsoup/29bf88aa66e6453211cc5064f9881023