Bash exit status not caught despite set -e and/or trap being active

Let's simplify it; minimum amount of code required for reproducing the issue you're dealing with is

set -e
: $((+)) # arithmetic expansion error
echo survived

According to the standard, this should never print survived, it says a POSIX shell running non-interactively shall immediately exit upon an expansion error. But seemingly Bash doesn't think so. Although this difference isn't explicitly documented in the man page, in description of POSIX mode it says

  1. Non-interactive shells exit if a syntax error in an arithmetic expansion results in an invalid expression.

We can say this means in its default operating mode, a non-interactive Bash session doesn't exit upon such error, but as you realized, it doesn't trigger errexit mechanism, or ERR trap either. Instead, it assigns a non-zero value to $? an moves on.

To overcome this and get the expected behavior, you should define reproduce as follows

function reproduce() (
    # Trigger arithmetic error on purpose
    a=$((1109962735 - hello=12272 + 1))

This way the expansion error will take place in a subshell and cause it to exit with a non-zero status, thus, errexit and trap will be able to catch it.

Upon dash-o's request, here is a version that sets a for the current execution environment when the expression is valid

function reproduce() {
    if : $((expression)); then

On surface, it looks as if bash will not trigger the trap on various SYNTAX errors. Only when it a command (external, built-in) is executed (and return non-zero), the ERR trap will be trigered.

From the man page:

If a sigspec is ERR, the command arg is executed whenever a pipeline (which may consist of a single simple command), a list, or a compound command returns a non-zero exit status, subject to the following conditions ...

The ERR trap only applies to PIPELINE. If bash identifies a syntax error, it aborts before executing the pipeline, therefore NO trap. Even though he documentation for '-e' specify the same condition (if a pipeline ... exit with non-zero status), the observed behavior is different.

If you try other expansions - e.g. command expansion- trap is triggered, as there is pipeline execution:

  • a=$(bad-commands)
  • a=$([)

If use try various syntax errors in arithmetic expansion, trap not triggered - there was no pipeline.

  • a=$((2+))
  • a=$((2 @))

Also, other bash Syntax error do not trigger the trap: (), [[ ]].

I could not find a solution that does not require extensive changes to the source script. May be file a bug/feature request with the bash team ?