Problem: Repeated printing of (parts of) the stack trace
Gist
- With subshells, several stack traces may be printed.
- All but the first stack trace can be ignored.
Problematic behaviour
- For each subshell in the current stack of subshells (including the root
shell), we get a stack trace.
- The
ERR
trap is caught for each subshell. - Reason: The trap on
ERR
returns non-zero, and so does the subshell.- If it returned zero, the error would be ignored on the upper level, and program execution continue, which is undesired.
- The
- More precisely, each subshell gives a stack trace on the stack from itself
up to the root shell.
- Reason: Subshells inherit the knowledge of its ancestors, but do not know of its descendant subshells.
- Thus, the closer to the root of the stack, the more often we get the (same) information printed; while the information on where the error originally occurred is printed only once.
Desired behaviour
- The whole stack trace is printed once, and nothing more.
- This would be achieved if only the subshell where the error originally occurred were to print a stack trace.
Considerations on fixes / improvements
Only print the stack trace for the root shell.
- This is easy, just check for
[[ $BASH_SUBSHELL -eq 0 ]]
. - This would mean that on error within a subshell, we do not get information below where the first subshell was invoked.
- This may be deemed acceptable if subshells are rarely used and/or only
with short local code within.
- Short local code would be, e.g.,
$(head -n 1 FILE)
. - Short local code would not be, e.g.,
$(local_nontrivial_function)
.
- Short local code would be, e.g.,
Reserve a special exit code.
- Let
$SPECIAL_EXIT_CODE
be some exit code distinct from0
and1
. - Change
ERR
trap totrap 'basic::on_error 0' ERR
, and defineon_error()
as follows:function basic::on_error() { [[ $? -eq $SPECIAL_EXIT_CODE ]] && exit 1 local -ri offset="$1" basic::print_stacktrace $((offset + 1)) exit $SPECIAL_EXIT_CODE }
- The idea is that
on_error()
only prints the stack trace in the lowest subshell, which has the full stack trace. - Naturally, this does not work properly if the original actual error had
$SPECIAL_EXIT_CODE
as exit code. - That is, we'd need an exit code that cannot occur anywhere else.
- This should be impossible in the general case.
Use a temporary file
- Use a temporary file to indicate whether
print_stacktrace()
was already called in a subshell. - This feels somewhat evil.
Inspect $BASH_COMMAND
- The
$BASH_COMMAND
variable contains the command executed that caused the trap. - We'd have to identify whether the command spawned a subshell (and the error came from there).
- Note that
(
is a valid command name---but$BASH_COMMAND
maintains any necessary quoting. - It might suffice to check for
(.*
and${varname_regex}=\$(.*
, given the other self-imposed restrictions.- Note, though, that there may be several assignments on a single line,
and thus within
$BASH_COMMAND
.
- Note, though, that there may be several assignments on a single line,
and thus within