1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# 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
### 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 `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 generally also caught in
`declare var=$undef_var` and `true $undef_var`.
* Compare [Subshell section](#Subshell).
* 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 -n` and `eval` with dynamic variable
names.
### Array variables
* Attempting to assign `var[0]=(foo bar)` or `var['x']=(foo bar)` (where
`$var` is 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](#Unset_variables).
* See also `src/basic.bash#errexit`.
* Attempting to assign `var['x']=foo` where `$var` is not an associative array
variable, has weird behaviour:
* In any case, the [(sub-)](subshell.md)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 errexit` is 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.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).
|