Skip to main content
2 of 4
Update output to show correct line number of the error

Why does a command with a non-zero exit status send an ERR signal even when it is “part of a && or || list”?

man bash includes this documentation for using trap:

trap [-lp] [[arg] sigspec ...]
    …
    The ERR trap is not executed if the failed command is part of the command list
    immediately following a while or until keyword, part of the test in an if statement,
    part of a && or || list, or if the command's return value is being inverted via !.
    These are the same conditions obeyed by the errexit option.
    …

I understand that “part of a && or || list” means that if a command is part of a list with these control operators, even if it has a non-zero exit status, it should not send an ERR signal (or exit the script if using set -o errexit).

And yet, here is a test script which seems to contradict this:

#!/usr/bin/env bash

_trap_err() {
    local status=$? sig=$1 line=$2;
    echo "Exit status ${status} on line ${line}: \`${BASH_COMMAND}\`";
}
trap '_trap_err ERR $LINENO' ERR;

function control_operators() {
    # The next line will send an ERR signal.
    [[ 1 -eq 2 ]] && echo Hello;
}

control_operators;

# The next line will not send an ERR signal.
[[ 1 -eq 2 ]] && echo Hello;

echo Done;

The output is:

❯ test.sh
Exit status 1 on line 14: `[[ 1 -eq 2 ]]`
Done

Because [[ 1 -eq 2 ]] is “part of a && or || list” it should not trigger an ERR signal.

The expected output is:

❯ test.sh
Done

Furthermore, only the list [[ 1 -eq 2 ]] && echo Hello inside the control_operators function sends the ERR signal, but the [[ 1 -eq 2 ]] && echo Hello outside the function does not.

The options set in the environment of this script are (output from set -o | grep 'on$'):

braceexpand     on
hashall         on
interactive-comments    on
xtrace          on

Questions:

  1. Is this behavior normal, or am I using control operators in a list incorrectly (or interpreting the documentation incorrectly)?
  2. What is the best way to avoid triggering an ERR trap when commands are part of a conditional expression and do not actually constitute an errored state?
    • One option is to add || true to the end of every list with control operators.
    • Another option is to use if [[ 1 -eq 2 ]]; then echo Hello; fi instead of the && shorthand; this does not cause an ERR signal to be sent, because (according to man bash) “part of the test in an if statement” – although I’m confused why this works when “part of a && or || list” doesn’t.
  3. Why does [[ 1 -eq 2 ]] send an ERR signal only when inside a function?