It's not the prettiest solution, but it does allow you to emulate set -e for the current shell as well as any functions and subshells:
#!/bin/bash # Set up an ERR trap that unconditionally exits with a nonzero exit code. # Similar to -e, this trap is invoked when a command reports a nonzero # exit code (outside of a conditional / test). # Note: This trap may be called *multiple* times. trap 'exit 1' ERR # Now ensure that the ERR trap is called not only by the current shell, # but by any subshells too: # set -E (set -o errtrace) causes functions and subshells to inherit # ERR traps. set -E f() { echo "[f] Start" >&2 echo "f:before-false1" echo "f:before-false2" false echo "f:after-false" echo "[f] Fail! I don't want this executed" >&2 } out=$(f)
Output (to stderr) if you call this script (exit code afterward will be 1) - note how the 2nd echo to stderr (>&2) is not printed, proving that the execution of false aborted the command substitution:
[f] Start
Note:
By design, set -e / trap ERR only respond to failures that aren't part of conditionals (see man bash, under the description of set (search for literal " set ["), for the exact rules, which changed slightly between Bash 3.x and 4.x).
Thus, for instance, f does NOT trigger the trap in the following commands: if ! f; then ..., f && echo ok; the following triggers the trap in the subshell (command substitution $(...), but not in the enclosing conditional ([[ ... ]]): [[ $(f) == 'foo' ]] && echo ok, so the script as a whole doesn't abort.
To exit a function / subshell explicitly in such cases, use something like || return 1 / || exit 1, or call the function / subshell separately, outside of a conditional first; e.g., in the [[ $(f) == 'foo' ]] case: res=$(f); [[ $res == 'foo' ]] - res=$(f) will then trigger the trap for the current shell too.
As for why the trap code may be invoked multiple times: In the case at hand, false inside f() first triggers the trap, and then, because the trap's exit 1 exits the subshell ($(f)), the trap is triggered again for the current shell (the one running the script).
cmd || return 1is explicit and conceptually ok, but so cumbersome...