12

Consider the commands

eval false || echo ok
echo also ok

Ordinarily, we'd expect this to execute the false utility and, since the exit status is non-zero, to then execute echo ok and echo also ok.

In all the POSIX-like shells I use (ksh93, zsh, bash, dash, OpenBSD ksh, and yash), this is what happens, but things get interesting if we enable set -e.

If set -e is in effect, OpenBSD's sh and ksh shells (both derived from pdksh) will terminate the script when executing the eval. No other shell does that.

POSIX says that an error in a special built-in utility (such as eval) should cause the non-interactive shell to terminate. I'm not entirely sure whether executing false constitutes "an error" (if it was, it would be independent of set -e being active).

The way to work around this seems to be to put the eval in a sub shell,

( eval false ) || echo ok
echo also ok

The question is whether I'm expected to have to do that in a POSIX-ly correct shell script, or whether it's a bug in OpenBSD's shell? Also, what is meant by "error" in the POSIX text linked to above?


Extra bit of info: The OpenBSD shells will execute the echo ok both with and without set -e in the command

eval ! true || echo ok

My original code looked like

set -e
if eval "$string"; then
    echo ok
else
    echo not ok
fi

which would not output not ok with string=false using the OpenBSD shells (it would terminate), and I wasn't sure it was by design, by mistake or by misunderstanding, or something else.

ctrl-alt-delor
  • 27,473
  • 9
  • 58
  • 102
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • `eval false` generates a non-zero status so I would expect `set -e` to terminate the script at that point. In the case of `!` `set -e` does not apply as `!` statement explicitly checks the exit status. – fcbsd Jun 12 '19 at 08:56
  • @fcbsd Would you expect `eval false` to terminate the script even if it's part of an AND-OR list or a conditional statement? I wouldn't. – Kusalananda Jun 12 '19 at 08:58
  • I'm not sure if `set -e` is set if that is the correct behaviour... I agree that it makes sense to not terminate in a conditional statement. – fcbsd Jun 12 '19 at 09:14
  • having played some more, with sh on CentOS 7 - I would say that is intended behaviour for OpenBSD's ksh/sh when using `set -e` so the ` ( )` is the answer. – fcbsd Jun 12 '19 at 09:59

2 Answers2

5

That no other shell needs such workaround is an strong indication that it is a bug in OpenBSD ksh. In fact, ksh93 doesn't show such issue.

That there is a || in the command line must avoid the shell exit caused by an return code of 1 on the left side of it.

The error of an special built-in shall cause the exit of a non interactive shell acording to POSIX but that is not always true. Trying to continue out of a loop is an error, and continue is a builtin. But most shells do not exit on:

continue 3

A builtin that emits a clear error but doesn't exit.

So, the exit on false is generated by the set -e condition not by the builtin characteristic of the command (eval in this case).

The exact conditions on which set -e shall exit are quite more fuzzy in POSIX.

  • This echoes the response that I got off the OpenBSD mailing list, but with more words, thanks! I'll sort out a proper bug report, and if nothing happens, I'll look at the source code myself. – Kusalananda Jun 13 '19 at 13:24
4

[sorry if this is not a real answer, I'll update it when I get round to it]

I had a look at the source code, and my conclusions are:

1) It's a bug/limitation, nothing philosophical behind it.

2) The "fix" for it from the portable fork of OpenBSD's ksh (mksh) is very poor, only making things worse, without really fixing it:

New bug, different from all the other shells:

mksh -ec 'eval "false; echo yup"'
yup

bash -ec 'eval "false; echo yup"'
(nothing)

Still not really fixed:

mksh -ec 'eval "set -e; false" || echo yup'
(nothing)

bash -ec 'eval "set -e; false" || echo yup'
yup

You can replace bash above with dash, zsh, yash, ksh93, etc.