0
set -eo pipefail

commandThatFails || exitFunction

exitFunction

So this script is running the exitMethod twice... I thought set -e exited immediately on any non zero exit code and set -o pipefail made sure that during the pipeline any failure is the final exit status code not the most recent command?

Therefore I thought :

  1. commandThatFails
  2. Executes exitFunction
  3. set -o pipefail returns non zero exit code as the first command fails
  4. Non zero exit code is detected by set -e and exits immediately

In the docs it states:

The shell does not exit if the command that fails is part of the command list immediately following a while or until keyword, part of the test in an if statement, part of any 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 status is being inverted with !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. A trap on ERR, if set, is executed before the shell exits.

I thought that the exitfunction is the command following the final || so therefore would be counted and picked up and exited immediately.

I can resolve the issue with:

commandThatFails || { exitFunction; exit 1; }

but it doesn't seem like the more elegant way to handle this, any thoughts appreciated!

gigapogg
  • 51
  • 6
  • 2
    See [BashFAQ #105](https://mywiki.wooledge.org/BashFAQ/105#Exercises). `set -e`'s behavior is wildly unintuitive and unportable (even between different versions of bash). It makes code harder to review, and more likely to behave differently between shell versions; is not generally recommended for use. – Charles Duffy Aug 05 '21 at 14:09
  • 2
    (https://www.in-ulm.de/~mascheck/various/set-e/ is another reference, showing just how many ways shells can implement `set -e` differently) – Charles Duffy Aug 05 '21 at 14:15
  • 2
    One other misunderstanding here is that `foo | bar || baz | qux` is one pipeline. It's _two_; `foo | bar` is one, and `baz | qux` is the other. – Charles Duffy Aug 05 '21 at 14:19

1 Answers1

5

|| is a flow control operator, not a pipeline component. pipefail has no effect on it.

If set -e caused flow control operators to exit, then you could never have an else branch of your script run with it active; it would be completely useless.

For that reason, && and || suppress set -e behavior for their left-hand sides, just like if condition; then success; else fail; fi suppresses that behavior for condition.


General practice is to make your exitFunction actually call exit, so you can write:

die() {
  rc=$?
  (( $# )) && printf '%s\n' "$*" >&2
  exit "$(( rc == 0 ? 1 : rc ))"
}

commandThatFails || die "commandThatFails failed"

...but if you want to call something else first, yes, you need to use a grouping, just as you did in the question.

Charles Duffy
  • 280,126
  • 43
  • 390
  • 441