# 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. * 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)`. ### Reserve a special exit code. * Let `$SPECIAL_EXIT_CODE` be some exit code distinct from `0` and `1`. * Change `ERR` trap to `trap 'basic::on_error 0' ERR`, and define `on_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](../../error-handling.md#Subshell). * Note, though, that there may be several assignments on a single line, and thus within `$BASH_COMMAND`.