Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Unofficial bash strict mode

Unofficial Bash Strict Mode

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.

Further explanation

set -e

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 *

Criticism of errexit

Rebuttal of criticism of errexit

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.

set -u

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.

set -o pipefail

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!

Inspired by

but without the stuff about changing IFS.

Discussion of that blog post: https://news.ycombinator.com/item?id=8054440

See also

ShellCheck helps find unsafe constructs in your shell script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment