help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Help-bash] subshell/eval terminates early with set -e when embedded


From: Bob Proulx
Subject: Re: [Help-bash] subshell/eval terminates early with set -e when embedded compound command fails early
Date: Sun, 10 Jul 2016 21:59:33 -0600
User-agent: Mutt/1.5.24 (2015-08-30)

John Calcote wrote:
> When I run this command:
> $ bash -x -c 'set -e; false && true; echo here'

You can make that more compact by putting -e in the option list.

  bash -e -x -c 'false && true; echo here'

But better is to never use -e since the rules by which it operates are
deep in legacy behavior that can't be changed now but make it
difficult to remember all of the details.  Like a cat it is subtle and
quick to anger.

> + set -e
> + false
> + echo here
> here

Yes.  That is what it should do.

> I see what I expect - false is shown to cause the compound command 'false
> && true' to bail out early, but the failure of the first sub-command in the
> compound command does not cause the entire sequence to terminate (because
> of set -e). Rather, 'here' is also echoed. This all makes sense to me -
> it's the way it's documented to work and the way I expect it to work.

Yes.  That is as documented.

  -e Exit immediately  if ...
     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 ||

Since "false && true" is part of a && list and it isn't the final
command then the false will NOT cause the shell to exit.  In a list
like that it is the final command in the pipeline that determines the
exit status.

> However, when I put 'false && true' in a subshell like this:
> 
> $ bash -x -c 'set -e; (false && true); echo here'
> + set -e
> + false
> 
> then only 'false' is displayed - 'here' is never echoed because false
> causes the subshell to exit with the last error code presented by the last
> command in the subshell.

Does what you expect.  Does what is documented.  The false doesn't
cause the subshell to exit immediately because it is part of a &&
list.  But since false was the last command executed it sets $? to 1
and therefore the sub-shell exists non-zero.  Then because the
subshell exited non-zero the main shell exited due to the -e setting.
All as documented and expected.

  bash -e -x -c '(false && true); echo here'

Same as:

  bash -e -x -c 'false; echo here'

No difference.  With -e you would certainly expect that to exit the shell.

> On the other hand - if I run this command:
> 
> $ bash -x -c 'set -e; (false && true; echo hi); echo here'

  bash -e -x -c '(false && true; echo hi); echo here'

But that is a completely different situation because it changes the $?
value of the subshell.  In the previous $? was 1 due to the last
command being false.  But in the above the last command is "echo here"
and that causes $? to be 0 which changes the exit value of the
sub-shell from non-zero to zero and therefore changes the parent shell
from exiting at that point versus not.  Which is what 'set -e' is
supposed to be doing so all is correct.

> + set -e
> + false
> + echo hi
> hi
> + echo here
> here

Yes.

> I now see 'here' from the outer shell - I presume this is because the
> semantics of compound commands does not allow set -e to fail the sequence
> in the subshell just because a non-terminal sub-command in the compound
> command failed.

Correct.

  bash -e -x -c '(false && true; echo hi); echo here'

Same as:

  bash -e -x -c '(echo hi); echo here'

Same as:

  bash -e -x -c 'echo hi; echo here'

So of course that continues through to the "echo here".

> The issue is that if the compound command is the last command in the
> sub-shell, it will return the failed sub-command's error code to the
> shell and that will, in turn, be passed as the result of the
> sub-shell.

There is no issue there.  It is documented to work that way for
historical legacy compatibility.  So all good and right with the
world.  Such as it is since we are talking about 'set -e' since one
should NEVER use 'set -e' in scripts.  It always bad and leads to
questions such as these because it never works the way people think it
might work if it were a different universe with a different history.
The best thing people can do is to stop using it.

If you don't want the shell to exit due to the last command in the
list returning non-zero then you must ensure that it can't be
non-zero.  Putting an "|| true" at the end is typical to ignore the
error code of the list before it.

  bash -e -x -c '(false && true || true); echo here'
    + false
    + true
    + echo here
    here

  bash -e -x -c '(false && false || true); echo here'
    + false
    + true
    + echo here
    here

  bash -e -x -c '(true && false || true); echo here'
    + true
    + false
    + true
    + echo here
    here

> This entire discussion also applies to eval - if I 'eval' a sequence of
> commands and the last one in the sequence is a compound command that fails
> early, then eval will receive (and return) the failed sub-command's result
> code.

Yes.  That is correct.  That is the correct behavior.  What else would
it do?  Not return the correct code?  I don't understand what you are
trying to communicate here.  You seem to be understanding it correctly.

> Frankly, I see why it's happening. I just don't know how to make it not
> happen - if I want to 'eval' an arbitrary (user-entered) command, and I

Eval'ing "arbitrary (user-entered) command" sets off alarm bells!
Security alert!  Security alert!  I assume you haven't created some
type of security vulnerability.  I will just keep going on assuming
that but it sounds so scary that I have a hard time not jumping on it
as a serious problem.

> want it to act like a shell normally acts, I really don't want eval to

Normally shells don't have 'set -e' set.

> return an error code if a compound command happens to be the last command
> in the eval sequence and it fails early.

If that is what you want then you must do one of two things.

1) Don't use 'set -e'.  Normal shells don't have it set.

  bash -x -c '(false && true); echo here'
    + false
    + echo here
    here

2) Override the return code.

  bash -e -x -c '(false && true) || true; echo here'
    + false
    + true
    + echo here
    here

> Any ideas would be appreciated.

Let me emphasize that 'set -e' is a road to heartache.  Worry about
eval'ing arbitrary user-entered code.

Bob



reply via email to

[Prev in Thread] Current Thread [Next in Thread]