Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I've run across some scripting like this recently:

( set -e ; do-stuff; do-more-stuff; ) || echo failed

This looks fine to me, but it does not work! The set -e does not apply, when you add the ||. Without that, it works fine:

$ ( set -e; false; echo passed; ); echo $?
1

However, if I add the ||, the set -e is ignored:

$ ( set -e; false; echo passed; ) || echo failed
passed

Using a real, separate shell works as expected:

$ sh -c 'set -e; false; echo passed;' || echo failed
failed

I've tried this in multiple different shells (bash, dash, ksh93) and all behave the same way, so it's not a bug. Can someone explain this?

share|improve this question
    
The `(....)``construct starts a separate shell to run its contents, any settings in it don't apply outside. –  vonbrand Feb 21 '13 at 3:13
    
@vonbrand, you missed the point. He wants it to apply inside the subshell, but the || outside the subshell affects the behavior inside the subshell. –  cjm Feb 21 '13 at 5:38
1  
Compare (set -e; echo 1; false; echo 2) with (set -e; echo 1; false; echo 2) || echo 3 –  Johan Feb 21 '13 at 15:48
add comment

2 Answers

up vote 2 down vote accepted

According to this thread, it's the behavior POSIX specifies for using "set -e" in a subshell.

(I was surprised as well.)

First, the behavior:

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.

The second post notes,

In summary, shouldn't set -e in (subshell code) operate independently of the surrounding context?

No. The POSIX description is clear that surrounding context affects whether set -e is ignored in a subshell.

There's a little more in the fourth post, also by Eric Blake,

Point 3 is not requiring subshells to override the contexts where set -e is ignored. That is, once you are in a context where -e is ignored, there is nothing you can do to get -e obeyed again, not even a subshell.

$ bash -c 'set -e; if (set -e; false; echo hi); then :; fi; echo $?' 
hi 
0 

Even though we called set -e twice (both in the parent and in the subshell), the fact that the subshell exists in a context where -e is ignored (the condition of an if statement), there is nothing we can do in the subshell to re-enable -e.

This behavior is definitely surprising. It is counter-intuitive: one would expect the re-enabling of set -e to have an effect, and that the surrounding context would not take precedent; further, the wording of the POSIX standard does not make this particularly clear. If you read it in the context where the command is failing, the rule does not apply: it only applies in the surrounding context, however, it applies to it completely.

share|improve this answer
    
Thanks for those links, they were very interesting. However, my example is (IMO) substantively different. Most of that discussion is whether set -e in a parent shell is inherited by the subshell: set -e; (false; echo passed;) || echo failed. It does not surprise me, actually, that -e is ignored in this case given the wording of the standard. In my case, though, I'm explicitly setting -e in the subshell, and expecting the subshell to exit on failure. There's no AND-OR list in the subshell... –  MadScientist Feb 21 '13 at 12:36
    
I disagree. The second post (I can't get the anchors to work) says "The POSIX description is clear that surrounding context affects whether set -e is ignored in a subshell." - the subshell is in the AND-OR list. –  Aaron D. Marasco Feb 21 '13 at 23:38
    
The fourth post (also Erik Blake) also says "Even though we called set -e twice (both in the parent and in the subshell), the fact that the subshell exists in a context where -e is ignored (the condition of an if statement), there is nothing we can do in the subshell to re-enable -e." –  Aaron D. Marasco Feb 21 '13 at 23:41
    
You're right; I'm not sure how I misread those. Thanks. –  MadScientist Feb 22 '13 at 5:00
add comment

I wouldn't rule out it's a bug just because several shells behave that way. ;-)

I have more fun to offer:

start cmd:> ( eval 'set -e'; false; echo passed; ) || echo failed
passed

start cmd:> ( eval 'set -e; false'; echo passed; ) || echo failed
failed

start cmd:> ( eval 'set -e; false; echo passed;' ) || echo failed
failed

May I quote from man bash (4.2.24):

The shell does not exit if the command that fails is [...] part of any command executed in a && or || list except the command following the final && or || [...]

Perhaps the eval over several commands leads to ignoring the || context.

share|improve this answer
    
Well, if all the shells behave that way it's by definition not a bug... it's standard behavior :-). We may lament the behavior as non-intuitive but... The trick with eval is very interesting, that's for sure. –  MadScientist Feb 21 '13 at 12:42
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.