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: John Calcote
Subject: Re: [Help-bash] subshell/eval terminates early with set -e when embedded compound command fails early
Date: Sun, 10 Jul 2016 23:15:43 -0600

Thanks for the reply Bob. I was not 100% accurate when I said "user entered
code". I really was trying to say "arbitrary code". The eval is part of a
sub routine and I didn't want to have to think about every parameter I pass
to the routine relative to 'set -e' funky legacy behavior.

I get it but, sadly, no one has come up with a new version of set -e
without the historical baggage so I'm stuck with it if I want a script that
exits on failure but I don't want to code every line in the script to check
status in its own special way.

I actually ended up solving the problem by tacking '; true' onto the
argument being eval'd. Similar to your solution, it just ensures the last
command in eval's argument is not a compound command that may fail in a
non-terminal sub-command.

Thanks again. Very useful info.

Regards,
John
On Jul 10, 2016 9:59 PM, "Bob Proulx" <address@hidden> wrote:

> 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]