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:
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.
The -e setting shall be ignored when executing the compound list following the
while
,until
,if
, orelif
reserved word, a pipeline beginning with the!
reserved word, or any command of an AND-OR list other than the last.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 executingecho 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
How to Start/Restart/Stop Apache Server on Linux as Non-Root User
Why Do I Get /Etc/Cups Conflicts Between Attempted Installs in Yocto
Cmsg_Nxthdr() Returns Null Even Though There Are More Cmsghdr Objects
Curl Command Doesn't Work in Bash Script
How Does Gdb Start an Assembly Compiled Program and Step One Line at a Time
Delete Folder That Contain Subfolders and Files on Linux
How to Modify Eip's Tracee Forked Procee
How to Get the Offset in a Block Device of an Inode in a Deleted Partition
How to Sort File Names by Specific Part in Linux
Use Bash Variable Within Slurm Sbatch Script
Install Binaries into /Bin, /Sbin, /Usr/Bin and /Usr/Sbin, Interactions with --Prefix and Destdir
Limit Output of All Linux Commands
Why Can't Cuda's Examples Makefile Find the Cuda Libraries
Linux Split a Column into Two Different Columns in a Same CSV File
Interacting with Files from Multiple Directories via Bash Script
How to Set a Non-Standard Baudrate on a Serial Port Device on Linux