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?

6
  • I can see both questions overlapping, but since my question focuses on compound commands and I don't understand the answer to my problem from reading the answers of the dupe, I feel both could stand on their own. Commented Sep 22, 2017 at 9:53
  • Reopened the question after substantial changes to the text of the question itself. It is no longer a dupe of the original set -e question. Commented Sep 22, 2017 at 10:25
  • @Kusalananda, I'm pretty sure there is a closer duplicate though. I remember seeing one quite recently. Commented Sep 22, 2017 at 11:17
  • See also mywiki.wooledge.org/BashFAQ/105 or fvue.nl/wiki/Bash:_Error_handling Commented Sep 22, 2017 at 11:20
  • @Kusalananda, Behavior of set -e in function calls is the on I had in mind (itself closed as duplicate). Commented Sep 22, 2017 at 13:56

2 Answers 2

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.

7
  • So my mistake is assuming, that the { ... } command list is treated as one command by the || command separator? Commented Sep 22, 2017 at 9: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. Commented Sep 22, 2017 at 9: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? Commented Sep 22, 2017 at 9:50
  • @Minix No, the return code is that of the last command in the list. Try with { echo "Hello"; echo "World"; false; } || echo "Mars". Commented Sep 22, 2017 at 9: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. Commented Sep 22, 2017 at 9:55
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.

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.