'How to make errexit behaviour work in bash functions

How do I get -e / errexit to work in bash functions, so that the first failed command* within a function causes the function to return with an error code (just as -e works at top-level).

* not part of boolean expression, if/elif/while/etc etc etc

I ran the following test-script, and I expect any function containing f in its name (i.e. a false line in its source) to return error-code, but they don't if there's another command after. Even when I put the function body in a subshell with set -e specified again, it just blindly steamrolls on through the function after a command fails instead of exiting the subshell/function with the nonzero status code.

Environments / interpreters tested:

I get the same result in all of these envs/shells, which is to be expected I guess unless one was buggy.

Arch:

  • bash test.sh (bash 5.1)
  • zsh test.sh (zsh 5.8)
  • sh test.sh (just a symlink to bash)

Alpine:

  • docker run -v /test.sh:/test.sh alpine:latest sh /test.sh (BusyBox 1.34)

Ubuntu:

  • docker run -v /test.sh:/test.sh ubuntu:21.04 sh /test.sh
  • docker run -v /test.sh:/test.sh ubuntu:21.04 bash /test.sh (bash 5.1)
  • docker run -v /test.sh:/test.sh ubuntu:21.04 dash /test.sh (bash 5.1)

Test script

set -e

t() {
    true
}

f() {
    false
}

tf() {
    true
    false
}

ft() {
    false
    true
}

et() {
    set -e
    true
}

ef() {
    set -e
    false
}

etf() {
    set -e
    true
    false
}

eft() {
    set -e
    false
    true
}

st() {( set -e
    true
)}

sf() {( set -e
    false
)}

stf() {( set -e
    true
    false
)}

sft() {( set -e
    false
    true
)}

for test in t f tf ft _ et ef etf eft _ st sf stf sft; do
    if [ "$test" = '_' ]; then
        echo ""
    elif "$test"; then
        echo "$test: pass"
    else
        echo "$test: fail"
    fi
done

Output on my machine

t: pass
f: fail
tf: fail
ft: pass

et: pass
ef: fail
etf: fail
eft: pass

st: pass
sf: fail
stf: fail
sft: pass

Desired output

Without significantly changing source in functions themselves, i.e. not adding an if/then or || return to every line in the function.

t: pass
f: fail
tf: fail
ft: fail

et: pass
ef: fail
etf: fail
eft: fail

st: pass
sf: fail
stf: fail
sft: fail

Or at least pass/fail/fail/fail in one group, so I can use that approach for writing robust functions.

With accepted solution

With the accepted solution, the first four tests give the desired result. The other two groups don't, but are irrelevant since those were my own attempts to work around the issue. The root cause is that the "don't errexit the script when if/while-condition fails" behaviour propagates into any functions called in the condition, rather than just applying to the end-result.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source