This Markdown file is 90% the output of error_handling.bash.
I wrote the script / this document to describe the shell options errexit, inherit_errexit, and errtrace
as well as the ERR trap and their conditions and interactions in more detail as this is what I always
failed to understand with the pieces of information I found so far.
- shell options: https://www.gnu.org/software/bash/manual/html_node/The-Set-Builtin.html
errexit(-e): exit on non-zero resulterrtrace(-E): inheritERRtrap
- additional shell option: https://www.gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
inherit_errexit: inheriterrexitsetting during command substitution
The remainder of what you will see is the execution of the command group
{ false; return 0; } in different variations and with different shell options enabled.
You can think of this group as function foo. It's basically the same but was less suitable for reasons of presentation.
Each of the following lines shows:
- optional list of enabled shell options during invocation
- invoked shell script
- exit status/code of the invocation
{ false; return 0; } ↩ 0
{ false; return 0; } && true ↩ 0
{ set -e; false; return 0; } ↩ 1
{ set -e; false; return 0; } && true ↩ 0
-errexit { false; return 0; } ↩ 1
-errexit { false; return 0; } && true ↩ 0
-errexit { set -e; false; return 0; } ↩ 1
-errexit { set -e; false; return 0; } && true ↩ 0The same invocations but this time command substituted:
The only difference: out="$(fail)" no longer fails which can be "healed" using the inherit_errexit option.
out=$( { false; return 0; }) ↩ 0
out=$( { false; return 0; }) && true ↩ 0
out=$(set -e; { set -e; false; return 0; }) ↩ 1
out=$(set -e; { set -e; false; return 0; }) && true ↩ 0
-errexit out=$( { false; return 0; }) ↩ 0
-errexit out=$( { false; return 0; }) && true ↩ 0
-errexit out=$(set -e; { set -e; false; return 0; }) ↩ 1
-errexit out=$(set -e; { set -e; false; return 0; }) && true ↩ 0
-inherit_errexit -errexit out=$( { false; return 0; }) ↩ 1
-inherit_errexit -errexit out=$( { false; return 0; }) && true ↩ 0
-inherit_errexit -errexit out=$(set -e; { set -e; false; return 0; }) ↩ 1
-inherit_errexit -errexit out=$(set -e; { set -e; false; return 0; }) && true ↩ 0The same restrictions apply to the ERR trap/signal, which
[...] is not executed if the failed command is part of the command list immediately [...]
These are the same conditions obeyed by the errexit (-e) option."
-- https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html#Bourne-Shell-Builtins
{ false; return 0; } ↩ 0
{ false; return 0; } && true ↩ 0
trap "exit \$?" ERR; { false; return 0; } ↩ 1
trap "exit \$?" ERR; { false; return 0; } && true ↩ 0The ERR trap is not inherited by default. This can be changed with the errtrace option:
out=$( { false; return 0; }) ↩ 0
out=$( { false; return 0; }) && true ↩ 0
trap "exit \$?" ERR; out=$( { false; return 0; }) ↩ 0
trap "exit \$?" ERR; out=$( { false; return 0; }) && true ↩ 0
-errtrace out=$( { false; return 0; }) ↩ 0
-errtrace out=$( { false; return 0; }) && true ↩ 0
-errtrace trap "exit \$?" ERR; out=$( { false; return 0; }) ↩ 1
-errtrace trap "exit \$?" ERR; out=$( { false; return 0; }) && true ↩ 0-
The
-e/errexitshell option and theERRtrap will trigger whenever- a pipeline (which may consist of a single simple command),
- a list, or
- a compound command returns a non-zero exit status, BUT:
-
Neither the
-e/errexitshell option nor theERRtrap is inherited if used inside a command substitution!- To inherit the
-e/errexitshell option use theinherit_errexitshell option. - To inherit the
ERRtrap use the-E/errtraceshell option.
- To inherit the
-
Neither the
-e/errexit shell option nor theERRtrap will trigger if- the failed command is part of the command list immediately following an
untilorwhilekeyword,
- part of the test following the
iforelifreserved words,
- part of a command executed in a
&&or||list (as demonstrated in the examples)- except the command following the final
&&or||,
- any command in a pipeline
- but the last, or
- if the command's return status is being inverted using
!.
- the failed command is part of the command list immediately following an
-
To make the
-e/errexitshell option and theERRtrap work reliably you will likely have to change you code style in order to avoid all the pitfalls described in 3).
Use the -e/errexit shell option only to improve your error handling
(preferably together with inherit_errexit).
- Do not rely on it.
- It easily fails in unintuitive situations where you would expect it to work
(
true && callworks;call && truedoes not). - Your script has to work without. The moment someone uses your script
you have little control on the effective
-e/errexitsetting.
Use the ERR trap for more fine-grained error handling
(preferably together with -E/errtrace).
- Do not rely on it.
- Improve the default logging to track down sources of errors more easily.
- The code sample below will output something like this:
✘ error-handling.bash: false ↩ 1 at main(/home/bkahlert/error-handling.bash:165)handle_error() { local esc_red esc_reset; esc_red=$(tput setaf 1 || true); esc_reset=$(tput sgr0 || true) printf " %s %s: %s %s\n at %s\n" "${esc_red-}✘${esc_reset-}" "${0##*/}" "$2" "${esc_red-}↩ $1${esc_reset-}" "$3" >&2 exit "$1" } trap 'handle_error "$?" "${BASH_COMMAND:-?}" "${FUNCNAME[0]:-main}(${BASH_SOURCE[0]:-?}:${LINENO:-?})"' ERR
- The code sample below will output something like this: