1. The "if myfunc" problem -- error checking is skipped. This is specified by POSIX, but it's undesirable.
2. The f() { test -d /tmp && echo "exists" } problem. The exit code of the function is probably not what you expect!
3. the local x=$(false) problem. This happens in all shells because of the definition of $?. (local is a shell builtin with its own exit code, not part of the language) This one is already fixed with shopt -s more_errexit in Oil.
4. the x=$(false) problem. This is a bash problem with command substitution. For example, dash and zsh don't have this problem. Test case:
bash -c 'set -e; x=$(false); echo should not get here'
Newer versions of bash fix it with inherit_errexit. Oil also implements inherit_errexit and turns it on by default! (in Oil, not OSH)
-----
So 1 and 2 relate to the confusing combination of shell functions and errexit.
And 3 and 4 relate to command subs. Oil has fixed these with opt-in OSH shell options (on by default in Oil), but not 1 and 2.
If you know of any other problems, please chime in on the bug!
> 2. The f() { test -d /tmp && echo "exists" } problem. The exit code of the function is probably not what you expect!
The exit code is 0 (assuming /tmp/, stdout, /bin/test and /bin/echo are all working correctly; with /tmp1 it's 1), as expected; is this referencing a bug in sh and/or bash that I've fixed locally and then forgotten about?
(Also, I'm pretty sure it should be:
f() { test -d /tmp && echo "exists"; }
unless the parse error for missing ';' was your point (I haven't bothered to fix that one, but maybe Oil has).)
I have seen multiple people point it out as confusing. The problem is that in the first case, there are two exit codes:
1. false (suppressed on LHS of &&)
2. true
And in the second case, there are 3:
1. false (suppressed on LHS of &&)
2. true
3. the exit code of the function/subshell, which is the exit code of the last statement, which is nonzero. This then CAUSES THE PROGRAM TO FAIL, whereas it didn't before.
(edit: removed incorrect code)
-----
So it's kind of like a "ghost" exit code created by the subshell/function! Which has an unexpected interaction with errexit.
Does that make sense?
I am not sure exactly how to fix it, but it will probably involve limiting && to stuff like this:
if test -d /tmp && test -d /tmp/foo; then echo yes; fi
And disallow it when standing alone, because it doesn't make much sense there, if errexit is on.
Feel free to chime in bug with any possible solutions or more problems.
----
(And yes Oil fixes the brace problem with shopt -s parse_brace, which is on by default in Oil! Try it out and let me know if you like it :) )
Not at all well, I'm afraid, but I think I understand:
f(){ false && true; } ; set -e ; f ; echo hi
# this works correctly (f returns false and errexit triggers)
set -e; false && true ; echo hi
# despite `false && true` failing, this doesn't trigger errexit
I was confused by the implication that the function version was what was wrong (rather than "false && true is broken, and you'd expect the function to work the same way"). Probably partly because I was confusing it with `command || true`, which is a idiomatic way to suppress errexit-like mechanisms by making the exit status always 0.
test -d nosuchdir && echo "warning: no directory";
instead of
false && true
You didn't want the function to fail. You just wanted to print a warning if the directory doesn't exist. But the whole function fails, while it doesn't for the if statement.
The whole thing is inherently confusing ... The language confuses success/failure and true/false. Both of those are stuffed into an exit code.
test -d is really for true/false, but then functions also have success/failure (did the last command succeed).
So there's no way for shell to really know what you want.
I think Oil might end up with something like "if catch foo" for success/fail, and "if boolean grep" for true/false.
> You just wanted to print a warning if the directory doesn't exist.
That code prints a warning if the directory does exist, actually ("-d FILE FILE exists and is a directory"). Did you mean something like:
test -e outfile && echo "trying to overwrite preexisting outfile"
> You didn't want the function to fail.
Uh, I kind of do, actually? If I wanted to discard the exit status I'd write:
test -e outfile && echo "trying to overwrite preexisting outfile" || true;
This is awkward, and suggests a proper if-then operator (perl-6-style `??` collides with globbing, but maybe `&?`?), but it's better then having something like:
mostly-silently mix old and new data together because mkfoo failed and the error code was swallowed.
> So there's no way for shell to really know what you want.
Yep, and if we have to pick one problem, things failing completely in a obvious and also easily-fixable way is usually a much less awful problem to have than silently corrupting data and state, especially when you've opted-in to a mechanism (like errexit) specifically designed to do the former.
https://github.com/oilshell/oil/issues/709
Summary of problems:
1. The "if myfunc" problem -- error checking is skipped. This is specified by POSIX, but it's undesirable.
2. The f() { test -d /tmp && echo "exists" } problem. The exit code of the function is probably not what you expect!
3. the local x=$(false) problem. This happens in all shells because of the definition of $?. (local is a shell builtin with its own exit code, not part of the language) This one is already fixed with shopt -s more_errexit in Oil.
4. the x=$(false) problem. This is a bash problem with command substitution. For example, dash and zsh don't have this problem. Test case:
Newer versions of bash fix it with inherit_errexit. Oil also implements inherit_errexit and turns it on by default! (in Oil, not OSH)-----
So 1 and 2 relate to the confusing combination of shell functions and errexit.
And 3 and 4 relate to command subs. Oil has fixed these with opt-in OSH shell options (on by default in Oil), but not 1 and 2.
If you know of any other problems, please chime in on the bug!