4

I'm trying to run this script:

#!/bin/bash -e

{ 
    echo "Doing something"; 
    will_fail                 # like `false` 

    echo "Worked"; 
} || echo "Failed"

To my surprise, will_fail failed, but I did not see "Failed" on my command line, but "Worked".

Why did the compound command not exit with error after will_fail failed?

Minix
  • 5,855

2 Answers2

13

Failed will not be printed because the exit status of the compound command is that of the last command executing in { ...; }, which is echo. The echo succeeds, so the compound command exits with an exit status of zero.

The following would output three strings:

{ echo "Do something"; echo "Worked"; false; } || echo "Failed"

From the POSIX standard:

Unless otherwise stated, the exit status of a command shall be that of the last simple command executed by the command.


There are several things happening here (summary):

  • You run with set -e active. This will cause the shell to exit if any command returns a non-zero exit status (broadly speaking). However, this does not apply here since the will_fail command is part of (compound command, which is part of) a || list (and not last in it).

    Again, from the POSIX standard (my emphasis):

    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.

  • The last simple command in the || list is echo "Failed". This is what determines the overall exit status of the compound command. Since it executes successfully (and since will_fail will not cause the shell to exit), the status will be zero, which means that the other side of || won't be executed.

Kusalananda
  • 333,661
  • So my mistake is assuming, that the { ... } command list is treated as one command by the || command separator? – Minix Sep 22 '17 at 09:48
  • @Minix It is treated as one command (a compound command), but set -e does not apply to the commands other than the last in a || list. – Kusalananda Sep 22 '17 at 09:49
  • But wouldn't the return code of the compound command be >0 if one of the commands in its list returns >0, as seen in the first example? – Minix Sep 22 '17 at 09:50
  • @Minix No, the return code is that of the last command in the list. Try with { echo "Hello"; echo "World"; false; } || echo "Mars". – Kusalananda Sep 22 '17 at 09:53
  • Why do I not get "Hello World" in the first example, then? It looks like the return code of false, not of echo "World" is used, there. EDIT: Ok, your last edit is the crucial information for me. I see. So by making the whole thing an OR list, the compound command is no longer under the supervision of -e, except for its last command. – Minix Sep 22 '17 at 09:55
  • @Minix Because there, the shell exits at false. Remember, it is not part of a || list. – Kusalananda Sep 22 '17 at 09:56
0

I think that the intro to the accepted answer (although I +1 ed for its thoroughness) is misleading and it does not explain this exact issue in the presence of set -e. The rest below the line contains the answer. I decided to write it because I myself came here for it and first got misled.

Failed will not be printed because the exit status of the compound command is that of the last command executing in { ...; }, which is echo. The echo succeeds, so the compound command exits with an exit status of zero.

What I think should be conveyed in the first place is that here, the set -e is disabled due to the position of the { ... } in the AND-OR list. If one removes the || echo "Failed" part, the compound command in { ... } will break immediately after the failing part, not even reaching the last command of { ... }

Compare

#!/bin/bash -e

{ echo "Doing something"; false echo "Worked"; }

prints nothing after Doing something and returns error exit, despite (here's why the answer is wrong) being a compound command inside of { ... } with echo being last.

While

#!/bin/bash -e

{ echo "Doing something"; false echo "Worked"; } || echo "Failed"

prints Worked after Doing something, does not invoke echo "Failed" and returns 0 exit status.

The reason is buried in the citation (provided deeper by the accepted answer): the set -e is disabled inside anything which is part of AND-OR command.

With trailing || echo "Failed", the {...} becomes part of AND-OR command and hence stops obeying set -e. The false in the middle does not affect the flow, and proceeds to echo "Worked", it returns 0 to the AND-OR, and subsequently to the calling script.

Without trailing || echo "Failed", the { ... } is itself a normal compound set of commands, it does not belong to the exceptions mentioned in POSIX, and obeys set -e. The execution stops when false is reached, and the flow of execution does not even reach echo "Worked" part, returning error to the calling script.

agronskiy
  • 101