Error handling
- A central feature of
basic.bashis to exit (non-zero) on error and provide a useful error message, namely a stack trace.- In many cases, the failing command will additionally print an error message on its own, which should be printed above the stack trace.
- Manual error handling is still best, but often too time consuming when calling many external commands and bash builtins that may fail, as is common with shell scripts (unless they are very small).
Limitations
Introductory notes
- Much of the below is from empirical evidence (aka testing).
Backgrounded process
- An error in a backgrounded process (e.g.,
false &) is ignored by the calling shell. - Failures within, e.g.,
{ false; } &will cause a stack trace to be printed, but the calling shell will not terminate. - It may be a good idea to use
waitto check on the return status. - It may further be a good idea to redefine the trap on
ERRwithin a backgrounded process to not print a stack trace.
Subshell
- Some errors in subshells are not caught (properly):
declare var=$(false)- Instead, write:
declare var; var=$(false)
- Instead, write:
true $(false)- Workaround: Use an intermediate variable.
true <(false),true >(false)- Workaround: Use a pipe where possible.
- See also: Pipes
- Workaround: Use a temporary file.
- See also: Backgrounded process
- Workaround: Use a pipe where possible.
Unset variables
- Unset arrays (when accessed with subscript
@or*) are never treated as an error.- To manually check for a non-empty array:
test -v var[@] - To manually check for an unset array,
declare -p varand/or${var@a}may be helpful. - To manually check whether a variable is declared as an array,
declare -p varand/or${var@a}may be helpful.- The latter will fail (due to
set -o nounset) if$varis empty or unset. ${var[@]@a}and${var[*]@a}behave weirdly:- If
$varis undeclared, evaluates to the empty array/string. - If
$varis declared as an array, but unset, evaluates toa. - If
$varis set to the empty array, evaluates to the empty array/string. - If
$varis set to a non-empty array, evaluates to the array (or space-separated list in case of${var[*]@a}) of as manyaas the array is long.
- If
- The latter will fail (due to
- To manually check for a non-empty array:
- An error on an unset variable (due to
set -o nounset) does not cause a stack trace to be printed in the current (sub-)shell, but only a simple message mentioning the file and line number.- If there is no subshell involved, no stack trace is printed at all.
- Note that unset variable errors are generally also caught in
declare var=$undef_varandtrue $undef_var.- Compare Subshell section.
- This does not hold if the shell is interactive (
bash -i).
- This is arguably not a big problem; such errors should mostly be quickly
spotted by simple testing (or some static analysis tool).
- Exceptions: Usage of
declare -nandevalwith dynamic variable names.
- Exceptions: Usage of
Array variables
- Attempting to assign
var[0]=(foo bar)orvar['x']=(foo bar)(where$varis an array variable or associative array variable, respectively) causes an error to be printed instead of a stack trace in the innermost (sub-)shell.- Compare behaviour of unset variables.
- See also
src/basic.bash#errexit.
- Attempting to assign
var['x']=foowhere$varis not an associative array variable, has weird behaviour:- In any case, the (sub-)shell where the error originally occurs does not print a stacktrace, but a simple error message.
- If at least one subshell is involved, all but the lowest subshell print a stack trace and all (sub-)shells exit correctly.
- If no subshell is involved, the error message is printed, but execution is not aborted.
- Note that unlike for the preceding case,
set -o errexitis irrelevant.
Pipes
- A pipeline is considered an error iff the last command returns non-zero.
- A different behaviour can be achieved by
set -o pipefail.basic.bashdeliberately does not set this. See there for details.
- To catch errors in a non-last command of a pipeline, one should either
consult the
${PIPESTATUS[@]}array variable, orset -o pipefailin a subshell. - Oftentimes, avoiding pipes may be the best option.
Conditionals
- A command that is evaluated as a condition (e.g.,
if cmd; then ...; fi), is never considered an error as the return code is instead used as a boolean condition. - In some cases it may be necessary for proper error reporting to do something
like the following:
if cmd ... else then if [[ $? -ne 1 ]] then return 1 fi ... fi - See also: Pipes
Other use of stderr
- The
basic.bashlibrary assumes thatstderris never redirected, except directly from external commands or shell builtins (e.g.,var=$(cmd 2>&1)).
Annoyances
- With subshells, there may be multiple stack traces printed.
