Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subshell created using (...) behaves differently from bash -c '...'

Tags:

bash

shell

unix

I use set -e inside (...) on command line but it doesn't exit the subshell when false command is executed.

Code 1:

( set -e; echo "$BASHPID: start"; false; echo "foobar"; date; ) && 
  echo "$BASHPID: ok" || echo "$BASHPID: nope"
9136: start
foobar
Wed, Apr 29, 2015  7:14:24 PM
7292: ok

However if I use bash -c for subshell creation then it behaves as I expect.

Code 2:

bash -c 'set -e; echo "$BASHPID: start"; false; echo "foobar"; date;' &&
  echo "$BASHPID: ok" || echo "$BASHPID: nope"
7880: start
7292: nope

Interestingly if I remove && and || part after subshell then (...) also behaves fine.

Code 3:

( set -e; echo "$BASHPID: start"; false; echo "foobar"; date; )
5940: start

So conclusions I am getting are:

  1. (...) behaves differently from bash -c
  2. (...) && false behaves differently from (...) and changes behavior of subshell as well.

Am I making some obvious mistake in interpreting this strange (at least for me) behavior?

like image 567
anubhava Avatar asked Oct 31 '22 05:10

anubhava


1 Answers

The behavior of set -e with && is intentional.

It explicitly doesn't trigger when the command is part of a && sequence.

From the POSIX spec:

-e

When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit with the following exceptions: The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.

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.

If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.

This requirement applies to the shell environment and each subshell environment separately. For example, in:

set -e; (false; echo one) | cat; echo two

the false command causes the subshell to exit without executing echo one; however, echo two is executed because the exit status of the pipeline (false; echo one) | cat is zero.

I assume the difference here then is whether the shell knows that fact.

(...) presumably does because the current shell executes it and bash -c presumably doesn't because that's a fully external process (or something).

That last bit is speculation and to my mind the sub-shell behavior here is entirely unhelpful and makes set -e unreliable (which is why many people suggest avoiding it).

Though it seems like this might also provide a sort of "escape hatch" for that reliability problem if an explicitly run shell doesn't have that issue with composition of scripts.

like image 105
Etan Reisner Avatar answered Nov 15 '22 06:11

Etan Reisner