According to the manual of the set builtin, 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 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 status is being inverted with
!.
Any of the above would work, except the first since you're not running a loop.
Though for readability it's probably better to use an if statement:
set -e
if x=$(echo a; false); then
echo "subshell worked"
else
echo "subshell failed"
fi
echo "x: $x"
Or you can invert the result:
bash -c 'set -e; ! x=$(echo a; false); echo "[$?]x:$x"' # [0]x:a
bash -c 'set -e; ! y=$(echo b; true ); echo "[$?]y:$y"' # [1]y:b
But beware that the result is now inverted. Fortunately bash doesn't exit even though a successful subshell is inverted.
Interestingly the following seems also work, by grouping the subshell invocation in a list, then invert the whole list, not just the subshell invocation:
bash -c 'set -e; ! { x=$(echo a; false); echo "[$?]x:$x"; }' # [1]x:a
This somehow matches the 3rd case above, which is also a list, except it's not using && or ||.
A Special Case with Inline Initialization
Note about a tricky case with function local variables, compare the two almost identical functions below:
f() { local v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }
In both functions, the subshell ends with a false command which causes it to fail, however, when executed, we'll get:
$ f # fooled by 'local' with inline initialization
output:data, status:0
$ g # good one, with separated declaration and initialization
output:data, status:1
Why?
In bash, local is actually a builtin command. When the output of a subshell is used to initialize a local variable, the exit status is no longer the one of the subshell, but that of the local command, which is 0 as long as the local variable gets declared.
See also https://stackoverflow.com/a/4421282/537554
echo $(), then it might be because the subshells' exit codes are ignored when the line - theechocommand - has an exit code (usually 0) of its own. – u1686_grawity Dec 02 '11 at 18:10if ! $(exit 1) ; then echo $?; fi, I get0. Not sureifis the way to go if you need to preserve that exit value. – Ron Burk May 04 '17 at 02:08AC_COMPILE_IFELSE. It may be as weak as an executable is produced. I'm trying to gather that information now at More robust AC_COMPILE_IFELSE feature testing? – jww Nov 08 '17 at 21:01if ! output=$(inner); then exit $?; fiwill exit with a return code of 0 because$?will give the return code of!instead of the return code ofinner. You could get the desired behavior withif output=$(inner); then : ; else exit $?; fibut that's obviously more verbose – SJL Apr 06 '18 at 14:56foo=$(...)was safelocal foo=$(...)would be safe but it is not. Took me a long time before I even consideredlocalmight be the issue. Edit: I see that now mentioned in ryenus' answer – user1169420 Oct 10 '21 at 20:47