Remix.run Logo
Intralexical 2 days ago

Beware that `set -e`/errexit has quite a few pitfalls.

For example, it's automatically disabled in compound commands that are followed by AND-OR command chains.

This can especially bite you if you define a function which you use as a single command. The function's behavior changes based on what you call after it, masking errors that occurred inside it.

  $ myfunc()(set -e; echo "BEFORE ERROR"; false; echo "AFTER ERROR")
  
  $ myfunc
  BEFORE ERROR
  
  $ myfunc || echo "REPORT ERROR!"
  BEFORE ERROR
  AFTER ERROR
It also doesn't always propagate to subshells. (There might be an additional, non-portable option to make it propagate, though.)

  $ (set -euo pipefail; x="$(echo A; false; echo B)"; echo "$x")
  A
  B
EXCEPT for when it sorta does respect the subshell status anyway:

  $ (set -euo pipefail; x="$(echo A; false; echo B; false)"; echo "$x")
  <no output>
(I think what's happening here is that the assignment statement gets the exit value of the subshell, which is the last command executed in it, without errexit. Subshell completes but top level shell exits.)

And Gods help you if you want to combine that with the `local` keyword, which also masks subshell return values:

  $ f()(set -euo pipefail; local x="$(echo A; false; echo B; false)"; echo "$x"); f
  A
  B

  $ f()(set -euo pipefail; local x; x="$(echo A; false; echo B; false)"; echo "$x"); f
  <no output>
I like shell scripts. I've yet to find anything with more convenient ergonomics for quickly bashing together different tools. But they're full of footguns if you need them to be truly robust.