# Error handling * A central feature of `basic.bash` is 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 ### 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 `wait` to check on the return status. * It may further be a good idea to redefine the trap on `ERR` within a backgrounded process to not print a stack trace. ### Subshell * Some errors in [subshells](subshell.md) are not caught (properly): * `declare var=$(false)` * Instead, write: `declare var; var=$(false)` * `true $(false)` * Workaround: Use an intermediate variable. * `true <(false)`, `true >(false)` * Workaround: Use a pipe where possible. * See also: [Pipes](#Pipes) * Workaround: Use a temporary file. * See also: [Backgrounded process](#Backgrounded_process) ### 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 var` and/or `${var@a}` may be helpful. * To manually check whether a variable is declared as an array, `declare -p var` and/or `${var@a}` may be helpful. * The latter will fail (due to `set -o nounset`) if `$var` is empty or unset. * `${var[@]@a}` and `${var[*]@a}` behave weirdly: * If `$var` is undeclared, evaluates to the empty array/string. * If `$var` is declared as an array, but unset, evaluates to `a`. * If `$var` is set to the empty array, evaluates to the empty array/string. * If `$var` is set to a non-empty array, evaluates to the array (or space-separated list in case of `${var[*]@a}`) of as many `a` as the array is long. * An error on an unset variable (due to `set -o nounset`) does not cause a stack trace to be printed in the current [(sub-)](subshell.md)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 also caught in `declare var=$undef_var` (compare [Subshell section](#Subshell)). * 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 -n` and `eval` with dynamic variable names. ### 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.bash` deliberately 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, or `set -o pipefail` in 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](#Pipes) ### Other use of `stderr` * The `basic.bash` library assumes that `stderr` is never redirected, except directly from external commands or shell builtins (e.g., `var=$(cmd 2>&1)`). ## Annoyances * With [subshells](subshell.md), there may be [multiple stack traces printed](error-handling/subshell/repeated-printing.md).