I'm finding that sourcing the following bash script does not cause the a sequence of commands to stop when pipelined with &&.
sourceme.sh:
#!/usr/bin/env bash
set -o errexit
set -o | grep errexit
echo "About to error from sourceme.sh..."
$(false)
echo "sourceme.sh did not exit on error (exit code: $?)"
This is the call to demonstrate the problem.
(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
The output I get from this is as follows.
errexit on
About to error from sourceme.sh...
sourceme.sh did not exit on error (exit code: 1)
sourceme.sh did not error (exit code: 0)
This is the opposite of what I expected, where I expected to see the the whole command die when $(false) is reached in sourceme.sh, producing the following outout.
errexit on
About to error from sourceme.sh...
Continuing the bizarreness, if I use ; in place of &&, then I would expect the command continue, but this is not what happens.
$ (source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
errexit on
About to error from sourceme.sh...
So when sourcing the file && behaves as I expect ; to behave, and ; behaves as I would expect to && to behave.
Where am I going wrong with my understanding of how this should work?
The idea, roughly stated, is that set -o errexit (or, equivalently, set -e) causes the shell to exit on uncaught errors. The fact that && follows source sourceme.sh means that the error is caught.
So, if the source and echo commands are connected with ;, the shell exits:
$ (source sourceme.sh ; echo "sourceme.sh did not error (exit code: $?)")
errexit on
About to error from sourceme.sh...
But, if the source and echo commands are connected with &&, then the shell assumes that you have anticipated the possibility of the source command failing and the shell does not exit:
$ (source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
errexit on
About to error from sourceme.sh...
sourceme.sh did not exit on error (exit code: 1)
sourceme.sh did not error (exit code: 0)
Much of the behavior of set -e may be considered odd or unexpected. For more examples, see Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?.
The behavior of set -e (errexit) is defined more precisely in man bash:
-e
Exit immediately if a pipeline (which may consist of a single simple command), a list, or a compound command (see SHELL GRAMMAR above), exits with a non-zero status. 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 following the if or elif reserved words, 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 value 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. This option applies to the shell environment and each subshell environment separately (see COMMAND EXECUTION ENVIRONMENT above), and may cause subshells to exit before executing all the commands in the subshell.
If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a
compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.
As documented in man bash as well as in help set, the following two commands are equivalent:
set -e
set -o errexit
What you are observing is this.
This command doesn't exit on error:
(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
However this command:
(bash sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
does exit so && is not the only reason of this weirdness. It is combination of source and && that makes it not to exit.
You have also noted that use of ; instead of && makes it exit as in following command:
(bash sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
This behaviour is actually dependent of bash version in use.
When I run this command on OSX bash 3.2.57(1)-release it does exit:
(source sourceme.sh && echo "sourceme.sh did not error (exit code: $?)")
errexit on
About to error from sourceme.sh...
However on bash versions 4.2 and above behaviour changes and set -e doesn't exit when used with &&.
So bottomline is that set -e is highly unreliable and one should avoid depending on it's behaviour in important shell scripts.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With