4

I am working on a Bash script with several functions. I would like to exit the script if any commands return an nonzero exit code, unless in a context where that code is being explicitly handled (such as in an if condition or before a || alternative). To make this easier, I have removed all use of sub-shells from my script, and enabled every error handling option I could find.

Unfortunately, I'm still encountering a common pattern where errors are being suppressed.

If a command in a function returns a nonzero exit code, but is not the last command in the function, and the function is being called as the first part of an && expression, the exit code is ignored.

#!/bin/bash

trap 'echo "Error occurred." && exit' ERR
set -o errexit;  # -e
set -o errtrace; # -E
set -o pipefail;

first-step-fails() {
    # These should be redundant, but are repeated to be certain and clear.
    trap 'echo "Error occurred." && exit' ERR
    set -o errexit;  # -e
    set -o errtrace; # -E
    set -o pipefail;

    false; # exit code 1 (failure)
    true;  # exit code 0 (success)

    echo "A is executed.";
}

first-step-fails && echo "B is executed.";
A is executed.
B is executed.

I didn't expect either echos to be executed following the call to false.


If the function isn't called in a && expression, the error is trapped:

#!/bin/bash

trap 'echo "Error occurred." && exit' ERR
set -o errexit;  # -e
set -o errtrace; # -E
set -o pipefail;

first-step-fails() {
    # These should be redundant, but are repeated to be certain and clear.
    trap 'echo "Error occurred." && exit' ERR
    set -o errexit;  # -e
    set -o errtrace; # -E
    set -o pipefail;

    false; # exit code 1 (failure)
    true;  # exit code 0 (success)
    echo "A is executed.";
}

first-step-fails ### && echo "B is executed.";
Error occurred.

If the failure is the last step in the function, the error isn't trapped but of course the function passes on its nonzero exit status and suppresses the outer echo.

#!/bin/bash

trap 'echo "Error occurred." && exit' ERR
set -o errexit;  # -e
set -o errtrace; # -E
set -o pipefail;

first-step-fails() {
    # These should be redundant, but are repeated to be certain and clear.
    trap 'echo "Error occurred." && exit' ERR
    set -o errexit;  # -e
    set -o errtrace; # -E
    set -o pipefail;

    false; # exit code 1 (failure)
    ### true;  # exit code 0 (success)
    ### echo "A is executed.";
}

first-step-fails && echo "B is executed.";
(no output)

How can I have my bash script exit when there's an nonzero exit status returned from a command called inside a function that's invoked on the left-hand-side of an && expression?

I have been testing on macOS (built-in GNU Bash 4.4.23) but need a solution that also works on Alpine Linux (packaged GNU Bash 4.4.19).

1

1 Answer 1

3

It looks to me as if this description of set -e:

The shell does not exit if the command that fails is ... part of any command executed in a && or || list

is liberally interpreting "part of a && list" to include every command within the function call. As a result, even a simple failing command inside the function does not trigger the ERR trap -- when it is called in an && chain.

If you need to stop execution of the function's commands when one of them fails, one possibility would be to chain them all within a block:

first-step-fails() {
    { 
      true &&
      false &&
      true;
    } || exit 1

    echo "A is executed.";
}

first-step-fails && echo "B is executed.";

... results in no output and a return-code of 1.

2
  • Darn. Okay, explicit and verbose it shall be. Thanks.
    – Jeremy
    Commented Dec 21, 2018 at 18:11
  • Would be good to now if "'is liberally interpreting" is true or if this is in fact POSIX's requirement.
    – doak
    Commented Jan 23, 2019 at 11:17

You must log in to answer this question.

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