How does "set -e" work with subshells?
Asked Answered
T

3

23

I was wondering whether set -e propagates through subshells (i.e. does a subshell inherit the -e setting of its parent), so I made some experiments. I found some strange results that I can't explain.

First, here are some basic tests. They return what I expect.

( true; false )         # 1
( false; true )         # 0
( set -e; false; true ) # 1

Now I tried what happens if I put a subshell within my subshell. This expression returns 1, which suggests that it propagates.

( set -e; ( false; true ) )

Then I tried these expressions. I expected them to return 1, but I found that they return 0.

( set -e; ( true; false ); true )
( set -e; ( set -e; false; true ); true )

Why? In both cases, the inner subshell returns 1, whether set -e propagates or not (as I checked in the beginning). The outer subshell has set -e, which means that it should fail after the inner subshell exits, but it does not. Can someone explain this?

Theomorphic answered 25/3, 2014 at 9:31 Comment(0)
A
14

Prior to bash 4, set -e appears to only cause the shell to exit if a simple command has a non-zero exit (emphasis mine):

-e Exit immediately if a simple command (see SHELL GRAMMAR above) exits with a non-zero status.

In bash 4 (possibly 4.1, I don't have a 4.0 to check), the effect of -e was extended to more complicated commands:

-e Exit immediately if a pipeline (which may consist of a single simple command), a subshell com- mand enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces (see SHELL GRAMMAR above) exits with a non-zero status.

Athenaathenaeum answered 25/3, 2014 at 13:25 Comment(1)
Nicely done; as of bash 4.3.30, the wording is :"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."Prophet
N
4

From man bash:

set [+abefhkmnptuvxBCEHPT] [+o option-name] [arg ...]

-e

Exit immediately if a pipeline (which may consist of a single simple command), a subshell command enclosed in parentheses, or one of the commands executed as part of a command list enclosed by braces (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 !. 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.

So when a false is found, it exits from the subshell.

See a couple of examples:

# when `false`, exit. Hence, no `echo ho`
$ ( set -e; echo hi; false; echo ho ) ; echo $?
hi
1

# when `false`, exit. Hence, no `echo hu`
$ ( set -e; echo hi; true; echo ho; false; echo hu ) ; echo $?
hi
ho
1
Nucleoplasm answered 25/3, 2014 at 9:45 Comment(5)
I can see that the particular subshell exits but not the outer one, but it doesn't follow from the documentation that it should work like this. It seems to work this way though, so I have to plan my script this way.Theomorphic
Mmm don't know if I understand you properly. In inner sub-shells the parameter is replicated -> ( set -e; ( true; false; echo 22 ) ; echo 55 ) won't print either 22 nor 55.Nucleoplasm
In bash 3.2, the 55 is echoed. In bash 4.1, it is not. This is possibly the result of a bug fix in bash 4 or 4.1, although I can't find anything related in the bash release notes.Athenaathenaeum
The wording for the -e option did change between 3.2 and 4. In 3.2, it only seems to apply to simple commands, not compound commands (like subshells) or pipelines and such.Athenaathenaeum
It's even more complicated: If you directly (with a conditional on the expression itself, not on $?) test the return code of the subshell, that affects how that subshell behaves. It (bash 3) doesn't bail out. ( set -e; false; echo inner ); [ $? -eq 0 ] || echo outer prints outer, ( set -e; false; echo inner ) || echo outer prints inner.Sistrunk
B
4

As of Bash 4.4, these commands all return 1:

( set -e; ( false; true ) )
( set -e; ( true; false ); true )
( set -e; ( set -e; false; true ); true )

So I think you may be using an older version of Bash which has different implementations about set -e and subshell commands, as pointed out by another answer.

Blacking answered 11/2, 2018 at 11:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.