Why Doesn't Set -E Cause a Failure with 'False || False && True'

Why doesn't set -e cause a failure with `false || false && true`?

set -e only bails on unchecked failures.

When you branch on a failure (using if, until, while, && or ||), that failure is checked.

If the specification were not written in this manner, short-circuiting boolean operations could not effectively be used for flow control because the false branches would always cause an exit.


To quote from the specification, with emphasis added:

When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit, as if by executing the exit special built-in utility with no arguments, with the following exceptions:

  1. The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.

  2. The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

  3. If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.

This requirement applies to the shell environment and each subshell environment separately. For example, in:

set -e; (false; echo one) | cat; echo two

the false command causes the subshell to exit without executing echo one; however, echo two is executed because the exit status of the pipeline (false; echo one) | cat is zero.

Note that this specification has changed over time; shells implementing a prior revision of the POSIX specification may not precisely comply with the version quoted here.


To inject some opinion here -- I'd strongly suggest reading BashFAQ #105 and ensuring that you fully understand all behaviors described therein before making the decision to use set -e rather than implementing explicit error-handling by hand. The FVUE wiki further describes the distinctions in behavior between set -e in bash-native mode and POSIX mode, which should likewise be understood.

Why does set -e; true && false && true not exit?

To simplify EtanReisner's detailed answer, set -e only exits on an 'uncaught' error. In your case:

echo "about to fail" && /bin/false && echo "foo"

The failing code, /bin/false, is followed by && which tests its exit code. Since && tests the exit code, the assumption is that the programmer knew what he was doing and anticipated that this command might fail. Ergo, the script does not exit.

By contrast, consider:

echo "about to fail" && /bin/false

The program does not test or branch on the exit code of /bin/false. So, when /bin/false fails, set -e will cause the script to exit.

Alternative that exits when /bin/false fails

Consider:

set -e
echo "about to fail" && /bin/false ; echo "foo"

This version will exit if /bin/false fails. As in the case where && was used, the final statement echo "foo" would therefore only be executed if /bin/false were to succeed.

Why does eval exit subshell mid-&& with set -e?

I should also mention that set -e is terribly error-prone; see http://mywiki.wooledge.org/BashFAQ/105 for a bunch of examples. So the best solution might be to dispense with it, and write your own logic to detect errors and abort.


That out of the way . . .

The problem here is that eval "false && true" is a single command, and evaluates to false (nonzero), so set -e aborts after that command runs.

If you were instead to run eval "false && true; true", you would not see this behavior, because then eval evaluates to true (zero). (Note that, although eval does implement the set -e behavior, it obeys the rule that false && true is non-aborting.)

This is not actually specific to eval, by the way. A subshell would give the same result, for the same reason:

$ bash -x -c 'set -e; (false && true); echo here'
+ set -e
+ false

The simplest fix for your problem is probably just to run an extra true if the end is reached:

$SSH $remote "set -e; $command; true"
eval "$command; true"

set -e and short tests

Regarding my problem of the script unexpectedly dying, it is due to running those short tests in a subshell, or from a function, and said function returns a value different than zero:

Original example:

$ set -e
$ false && true
$ echo $?
1

Using a subshell or function:

$ set -e
$ (false && true)
<script died>

$ set -e
$ variable=$(false && true)
<script died>

$ set -e
$ foo()(false && true)
$ foo
<script died>

$ set -e
$ foo(){ false && true; }
$ foo
<script died>

Possible solutions:

$ set -e
$ (false && true ||:)
$ (false && true) ||:
$ (if false; then true; fi)

Why does set -e cause my script to exit when it encounters the following?

Redirecting error messages to /dev/null does nothing about the exit status returned by the script. The reason your ls command isn't causing the error is because it's part of a pipeline, and the exit status of the pipeline is the return value of the last command in it (unless pipefail is enabled).

Given your update, it looks like the command that's failing is the last grep in the pipeline. grep only returns 0 if it finds a match; otherwise it returns 1, and if it encounters an error, it returns 2. This is a danger of set -e; things can fail even when you don't expect them to, because commands like grep return non-zero status even if there hasn't been an error. It also fails to exit on errors earlier in a pipeline, and so may miss some error.

The solutions given by geocar or ephemient (piping through cat or using || : to ensure that the last command in the pipe returns successfully) should help you get around this, if you really want to use set -e.

Waiting for multiple processes in bash with set -e

From bash manual section about usage of set

-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.

tl;dr

In a bash script, for a command list like this

command1 && command2

command1 is run in a separate environment, so it cannot affect the script's execution environment. but command2 is run in the current environment, so it can affect

bash one-line conditional fails when using set -e

Use

[ -n "$var" ] || die "result is empty"

This way, the return value of the entire statement is true if $var is non-empty, so the ERR trap is not triggered.

What does set -e mean in a bash script?

From help set :

  -e  Exit immediately if a command exits with a non-zero status.

But it's considered bad practice by some (bash FAQ and irc freenode #bash FAQ authors). It's recommended to use:

trap 'do_something' ERR

to run do_something function when errors occur.

See http://mywiki.wooledge.org/BashFAQ/105



Related Topics



Leave a reply



Submit