1

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?

Update: the first answer by @Kusalananda is correct for the example above (adding return 0 to the control_operators function prevents the ERR signal). However, here is another example which seems to violate the “part of a && or || list” rule:

#!/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;
true && false;
echo Done;

The output of this script is:

❯ test.sh
Exit status 1 on line 7: `false`
Done

The line true && false should not send an ERR signal because the command false is “part of a && or || list”. So, why did it?

Update 2: I'm working on macOS with bash 3.2, which is why the snippet above from man bash fails to mention “ except the command following the final && or ||”. 😵‍💫 Thanks again to @Kusalananda for providing the missing details.

4
  • @ilkkachu I'm on macOS Monterey, which comes with bash version 3.2.57. I know I can update bash, but I want my scripts to be able to run on other macOS computers which I must assume will have bash 3. 😭 Commented Apr 14, 2022 at 20:13
  • right, they seem to have had a lapse there. It does continue with "These are the same conditions obeyed by the errexit option.", so the intent seems clear (and the behaviour right) even with that omission. (Also the command line help trap doesn't seem to mention ERR at all in 3.2.)
    – ilkkachu
    Commented Apr 14, 2022 at 20:16
  • 1
    Elsewhere in the bash 3.2 man page, this is mentioned: “The return status of AND and OR lists is the exit status of the last command executed in the list.” It is useful insight to know that the list itself can have an exit status, derived from the last command of the list. Commented Apr 14, 2022 at 20:20
  • Basically everything has to have some sort of exit status, so that the shell has something to put into $? and to pass along when returning from a function or exiting from a script (either with exit/return or just falling off the end).
    – ilkkachu
    Commented Apr 14, 2022 at 20:24

1 Answer 1

3

Your script is triggering the ERR trap due to the return status of the control_operators call, which is non-zero. The test in the function does not directly trigger the trap.

The trap output indicates this by the line number it prints, which will be the line with the call to the function.

The trap output also indicates where this non-zero exit status comes from, which is the result of the test in the function. Since the test is the last thing executed in the function, this sets the function's exit status.

The function call is not part of an AND or OR list, so the trap is triggered.


For you updated question, you forget that the ERR trap will still be called if the command that returns the non-zero exit status is the last command in the AND or OR list.

From the bash manual, with my emphasis:

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 command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return value is being inverted using !.

Your example is true && false, and since false is last in the AND list, it triggers the ERR trap.

Another way of saying this is that the ERR trap is called if the command that decides the exit status of a list or pipeline returns a non-zero exit status (or zero exit status if ! is used with that command).

5
  • 1
    You're right! It's obvious in hindsight. If I add return 0; to the end of the control_operators function, no ERR signal is sent. Commented Apr 14, 2022 at 19:48
  • @QuinnComendant That would avoid calling the trap handler, yes. I would not call ERR a "signal", though, as it's not (the documentation calls EXIT, DEBUG, ERR, and RETURN "traps"). I would personally also avoid return 0 if there was any chance that I would, at any point in time, need to test for successful completion of that function call. I would opt for control_operators || true instead. Also note that most of your ; are not needed.
    – Kusalananda
    Commented Apr 14, 2022 at 19:55
  • I've added a second example test script which still seems to violate the “part of a && or || list” rule. I don't see how @Kusalananda’s answer solves this one. Commented Apr 14, 2022 at 19:58
  • @QuinnComendant See updated answer.
    – Kusalananda
    Commented Apr 14, 2022 at 20:00
  • Thanks @Kusalananda! On macOS, for its default bash 3.2, man bash does not include the note about the exception for commands following the final && or ||. I agree, adding ` || true` after command lists which normally exit non-zero is the best option. Commented Apr 14, 2022 at 20:10

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .