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?
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.
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
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.