14

I thought set -e had the same effect on subshells as on top-level shell. Apparently, it does not. This:

(
  set -e
  false 
  true
) || echo false1


bash -ec '
  set -e
  false 
  true
' || echo false2


bash <<EOF || echo false3
  set -e
  false 
  true
EOF

bash <<EOF || echo false4
  false 
  true
EOF

bash <<EOF || echo false5
  false  &&
  true
EOF

Prints

false2
false3
false5

Where is this documented? Can I get subshells to terminate on errors, without connecting all their commands with && (or without doing || exit $? after each command)?

Edit:

My particular use case was something like:

set -e
  # ...
status=0
( false; true ) || status=$?
report_code $status
return $status

Where the contents of the subshell was my actual code. The problem with this is it always sets status to 0 and replacing || with ; causes an unwanted error exit because of the outer set -e.

I solved it with:

set -e
  # ...
set +e
( false; true ); status=$?
set -e
report_code $status
return $status

I wish I didn't have to do this, but it appears all common shells show this execed-subshell vs just-forked-subshell dichotomy:

#!/bin/sh

echo FORK\'D:
export SH
for SH in dash bash ksh zsh; do
    $SH -c 'st=0; ( set -e; false; true ) || st=$?; printf "%s\t%s\n" $SH  $st; '
done

echo EXEC\'D:
for SH in dash bash ksh zsh; do
    $SH -c 'st=0; '$SH' -c " set -e; false; true " || st=$?; printf "%s\t%s\n" $SH $st; '
done

OUTPUT:

FORK'D:
dash    0
bash    0
ksh 0
zsh 0
EXEC'D:
dash    1
bash    1
ksh 1
zsh 1
Petr Skocik
  • 28,816

1 Answers1

18

Observe:

$ ( set -e; false ; true ) || echo false1
$ ( set -e; false ; true ) ; echo code=$?
code=1

Also:

$ ( set -e; false ; true; echo inside=$? ) || echo false1
inside=0

Apparently, when the subshell is followed by a ||, set -e does not cause the subshell to exit upon reaching the false command. Instead, the subshell continues and executes true (and echo inside=$?).

The philosophy of set -e is typically that it only exits upon uncaught errors. Here, the presence of || outside the subshell seems to tell the shell that the error inside the subshell is 'caught' and therefore set -e does not cause an exit after false.

set -e has many surprising behaviors. See "Why doesn't set -e do what I expected?"

Documentation

The behavior above is hinted at in the documentation in man bash:

-e

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. 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 !. If a compound command other than a subshell returns a non-zero status because a command failed while -e was being ignored, the shell does not exit. 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. [Emphasis added.]

John1024
  • 74,655
  • "The philosophy of set -e is typically that it only exits upon uncaught errors. Here, the presence of || outside the subshell seems to tell the shell that the error inside the subshell is 'caught' and therefore set -e does not cause an exit after false." Yes, but the error inside the subshell is not reported. "false1" is not printed. It just masks the error, as if set -e was not in effect at all. – Jonas Berlin Jul 17 '18 at 17:49
  • You appear to be talking about the case ( set -e; false ; true ) || echo false1. The subshell exits with the return code of the last command run. Since || suppresses the the exit after false, the last command executed in the subshell is true. Thus false1 is not printed because the subshell exits with return code zero. – John1024 Jul 17 '18 at 18:40