16

According to this accepted answer using the set -e builtin should suffice for a bash script to exit on the first error. Yet, the following script:

#!/usr/bin/env bash

set -e

echo "a"
echo "b"
echo "about to fail" && /bin/false && echo "foo"
echo "c"
echo "d"

prints:

$ ./foo.sh 
a
b
about to fail
c
d

removing the echo "foo" does stop the script; but why?

Community
  • 1
  • 1
Marcus Junius Brutus
  • 26,087
  • 41
  • 189
  • 331

3 Answers3

10

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.

John1024
  • 109,961
  • 14
  • 137
  • 171
  • As such, do I understand correctly that in such cases the onus is on the programmer to manually append a `|| exit 1` at the end of such chains? – Marcus Junius Brutus Sep 11 '14 at 19:07
  • 1
    I am accepting this answer because the rationale given explains why the bash script **will** exit if the failing command is the last one in a `&&` chain – Marcus Junius Brutus Sep 11 '14 at 19:30
  • @MarcusJuniusBrutus I added a section that shows an alternative that might serve your needs better than `|| exit 1`. – John1024 Sep 11 '14 at 19:37
  • Except that the "rationale" given here is entirely unrelated to reality in that this has nothing to do with assumptions about what the programmer did or did not know about what they were doing. The reason is because of defined shell behaviour (under-specified but defined behaviour, which apparently finally got specified in the man page recently). – Etan Reisner Sep 11 '14 at 20:02
  • @EtanReisner Your answer excellently and definitively provided documentation on the defined shell behavior. My answer is an attempt to explain _why_ the developers chose that defined behavior. The intent is to complement, not contradict, your answer. – John1024 Sep 11 '14 at 20:19
  • @John1024 My apologies, I was not directing that at you as much as at MarcusJuniusBrutus for his reasoning in selecting this answer. I don't object to your answer in any real way. – Etan Reisner Sep 11 '14 at 20:23
  • @EtanReisner you may have a point but I made the decision to accept that answer (not without some thought) even though it expands upon your own as it provides what seems to me like a justification for the shell's behavior (which would otherwise seem arbitrary). – Marcus Junius Brutus Sep 11 '14 at 21:43
  • may I add the ability for `echo "foo"` to only be executed if the command before that succeeded: `echo "about to fail" && /bin/false; [ $? -eq 0 ] && echo "foo"` – Evi1M4chine Sep 17 '16 at 21:51
  • So what is the point of chaining commands with && if the functionality is explicitly ignored? – stu Sep 22 '16 at 20:59
  • If bash -e causes short circuit boolean evaluation to not function, then what's the point of writing statements that are intended to make use of boolean short circuit evaluation. As in, it will process every command in the boolean expression anyway, you might as well just enter them as separate commands (a; b; c) and not confuse the unfortunate future reader. – stu Sep 23 '16 at 18:36
  • sorry, I just tested it out and realized I misunderstood the question. It's not about the execution of the commands in one bash command, but the further execution of other commands in the script. I wrote a test script and I see what the functional difference is and I see now is has nothing to do with boolean short circuit evaluation. Sorry for the distraction. – stu Sep 23 '16 at 18:42
  • @stu Very good. (As clean-up, then, we may want to delete these comments.) – John1024 Sep 23 '16 at 18:45
6

Because that answer is not sufficiently specific enough.

It should say (bolded text is my addition):

# Any subsequent simple commands which fail will cause the shell script to exit immediately

Since the man page reads thusly:

-e      Exit  immediately if a simple 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 in an if statement, part of a && or ││
        list, or if the command’s return value is being inverted
        via  !.   A  trap on ERR, if set, is executed before the
        shell exits.

And SHELL GRAMMAR expands thusly:

SHELL GRAMMAR
   Simple Commands
       A simple command is a sequence of optional  variable  assignments  fol-
       lowed  by  blank-separated  words and redirections, and terminated by a
       control operator.  The first word specifies the command to be executed,
       and  is  passed  as  argument  zero.  The remaining words are passed as
       arguments to the invoked command.

       The return value of a simple command is its exit status,  or  128+n  if
       the command is terminated by signal n.
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • 1
    well, wanting to play the "man page lawyer", apparently the shell **does** exit if the failing command is the last one on a `&&` list so the part that reads "`does not exit if the command [..] is part of a && [..] list`" is not strictly correct – Marcus Junius Brutus Sep 11 '14 at 19:05
  • 1
    @MarcusJuniusBrutus The documentation for `-e` explicitly covers that detail. "The shell does not exit if the command that fails is ... part of a && or ││ list" though that is under-specific about what being "part of a ... list" means. It seems to mean not the final unit of such a list. – Etan Reisner Sep 11 '14 at 19:10
  • 3
    The man page for `bash` 4.3 finally clarifies this: "[...] part of any command executed in a && or || list **except the command following the final && or ||** [...]" – chepner Sep 11 '14 at 19:24
5

I came across set -e for Bash scripts but had problems understanding what happens regarding the evaluation of the command following the last && or || in a && or || list. I know of the following quote from http://man7.org/linux/man-pages/man1/bash.1.html about set -e:

The shell does not exit if the command that fails is (...) part of any command executed in a && or || list except the command following the final && or || (...)

To test this, I wrote a small Bash script:

#!/bin/bash

bash -c "set -e ; true           ; echo -n A"
bash -c "set -e ; false          ; echo -n B"
bash -c "set -e ; true  && true  ; echo -n C"
bash -c "set -e ; true  && false ; echo -n D"
bash -c "set -e ; false && true  ; echo -n E"
bash -c "set -e ; false && false ; echo -n F"
bash -c "set -e ; true  || true  ; echo -n G"
bash -c "set -e ; true  || false ; echo -n H"
bash -c "set -e ; false || true  ; echo -n I"
bash -c "set -e ; false || false ; echo -n J"

echo ""

It prints:

ACEFGHI

About A:

true does not have a non-zero status. Therefore, the shell doesn't exit.

About B:

false does have a non-zero status and is not part of a && or || list. Therefore, the shell exits.

About C:

This is a && or || list. We will have to look at the command following the last && or ||. The command is true which does not have a non-zero status. So it doesn't matter if the command is evaluated or not - the shell doesn't exit either way.

About D:

This is a && or || list. We will have to look at the command following the last && or ||. This time, the command is false which does have a non-zero status. So we have to check if that false is being evaluated - which is indeed the case since && is following the true. Therefore, the shell exits.

About E:

Same reasoning as with C: true is the command following the last && or ||. Therefore, the shell doesn't exit.

About F:

Similar to D: This is a && or || list. We will have to look at the command following the last && or ||. Again the command is false which does have a non-zero status. But this time it doesn't matter, because the first command is false as well. Since it's a && list, the second false won't be evaluated. Therefore, the shell doesn't exit.

About G:

Same reasoning as with C or E: true is the command following the last && or ||. Therefore, the shell doesn't exit.

About H:

This is a && or || list. We will have to look at the command following the last && or ||. This command is false which does have a non-zero status, but it won't be evaluated since || is preceded by true. Therefore, the shell doesn't exit.

About I:

Same reasoning as with C, E or G: true is the command following the last && or ||. Therefore, the shell doesn't exit.

About J:

This is a && or || list. We will have to look at the command following the last && or ||. This command is false which does have a non-zero status. Since || is preceded by false the second false will be evaluated. Therefore, the shell does exit.


You should be able to apply these test cases to your case: true && false && true. Since the command following the last && or || is true which doesn't have a non-zero status, it doesn't matter what precedes && or ||, the shell won't exit either way.

finefoot
  • 9,914
  • 7
  • 59
  • 102