In short: This is a question about an ambiuous paragraph in the POSIX
standard about how shells handle "set -e". My interpretation is, that
modern POSIX conforming shells do follow a wrong path due to this.
FYI: I subscribed here to ask about following topic:
URL http://lists.gnu.org/archive/html/bug-bash/2008-05/msg00081.html
URL http://lists.gnu.org/archive/html/bug-bash/2008-05/msg00084.html
I try to make it fully clear, therefor this post is so long even that
I tried to make it as short as possible. And if it is OT or a FAQ
please blame me for not being able to find the reference ;)
The motivation behind this eMail is the observation that follwing
command prints "wrong", while I would expect "correct" (Debian Etch,
Ubuntu 6.06 LTS) [0]:
bash -ec 'set -e; ( set -e; /bin/false; echo wrong; ) || echo correct'
Eric Blake pointed me to
URL http://www.opengroup.org/onlinepubs/009695399/utilities/set.html
which states [1]:
"When this option is on, if a simple command fails for any of the
reasons listed in Consequences of Shell Errors or returns an exit
status value >0, and is not part of the compound list following a
while, until, or if keyword, and is not a part of an AND or OR list,
and is not a pipeline preceded by the ! reserved word, then the shell
shall immediately exit."
He also notes that this passage is slightly ambiguous. Therefor I ask
here if the behavior [0] noted above is conforming to POSIX or not?
I would vote for "no", however I do not have a vote here. ;)
To go into detail:
The part "( set -e; /bin/false; echo wrong; )" is executed in a
sub-shell environment, and therefor in a shell. This shell (the
sub-shell) runs with "set -e" active. It encounters a command
(/bin/false) with an exit status of 1. The command is not subject to
the restrictions noted in paragraph [1].
So the sub-shell should immediately exit. But it does not, because
the parent shell executed the sub-shell as part of a restriction to
option -e.
So what seems to be unclear is, is a sub-shell independent of the
parent shell (in respect of paragraph [1]) or must it execute in the
context of the parent? Please note, that I do not think that a
sub-shell (parantheses) is a "simple command" as noted in this
paragraph. However I do not think it will not help to clarify this
alone, the problem seems to be a bit deeper (see below).
I think, the sub-shell (if not any non-simple command) shall be
independent of the parent shell in respect to "set -e". The
motivation behind this request is:
- This is more natural IMHO. I think paragraph [1] was introduced not
to disable "set -e" for the complete inner execution unit, but to not
make the shell exit if the inner execution unit is terminated
prematurely because of -e. AFAICS, this is where the ambiguity
starts which was noted by Eric Blake. (See the 2nd URL from the start
and [2] below.)
- A code fragment shall not surprisingly change it's behavior
drastically if called differently. This reveals that the problem is
not at the sub-shell level, it's far more intrinsic. Imagine
following shell snippet [2] (note that set -e is only used within the
function):
run()
{
set -e
false
echo wrong
}
run || echo "correct"
run; [ 0 = $? ] || echo correct
In the second-last line will output "wrong", the last line will output
"correct". Now change the function "run" into a separate shell script
(named "run"), and you will completely modify the output, because you
will get two times "correct" (as now "set -e" works as expected).
This behavior is very surprising. I would expect to see no "wrong" at
all. However if you follow paragraph [1] literally the shell might
even exit (as "false" is not part of any of the noted restrictions)
which would be completely wrong. So perhaps the paragraph is not only
ambiguous but even misleading?
- My interpretation is, that "set -e" is something like a saftety
belt, which tells the shell to not execute further as soon as some
command does not run as expected (and therefor has a nonzero exit
status). However this idea is completely void if any simple
conditional completely disables "set -e" throughout any inner
execution unit. So I even think this has some security impact, and
therefor shall be clarified.
- Some shell impementations (like bash version 2) handle that case
differently (say: Correct in my interpretation). So there should be
some clarification, which interpretation of the standard is correct
and which not.
- "set -e" shall be usable everywhere in a shell, to terminate the
processing in case a command does fail. However with the way Bash 3
(and nearly all POSIX shells) implement this option, it becomes
largely useless. It only is helpful for minimalistic scripts. But in
more complex situation it becomes very tedious to use this option.
Think about following snippet [3a]:
set -e
...
create()
{
some command
mkdir "$1" >/dev/null
some other command
}
...
rm -rf tmpdir1
create tmpdir1
...
create tmpdir2 ||:
...
The "||:" is a common technique to escape the "exit" rule of "set -e"
(also it looks like an adequate smiley, saying "erhm, this command may
fail"). However with an inlined create() function as above this does
not work as intended. So you have to write something like [3b]:
set -e
...
create()
{
set -e
some command
mkdir "$1" >/dev/null
some other command
}
...
rm -rf tmpdir1
create tmpdir1
...
set +e
( create tmpdir2 )
set -e
...
However this brings you in a maintainance hell, look at all those
additional "set" lines, which clutter the code and "surprisingly"
enables option e again.
To come to an end:
While you can argue, that above shell snipped [2] is correct according
to the noted paragraph [1] (however I would say that "run" is not a
simple command anymore), I do not think that following is covered, too
(it will still output "wrong" under bash 3):
run()
{
set -e
false
echo wrong
}
( run ) || echo "correct"
If you think of the Python way, Scripting environments shall give as
few surprising results as possible.
So the paragraph should be clarified. My interpretation is, that with
option -e in effect the shell does *not* exit immediately.
Instead it shall break the execution at that point and propagate the
result to the next outer conditional (given as restriction in
paragraph [2]). If there is no such outer conditional the shell shall
propagate the result to become the exit code of the shell (and thus
exit).
At least that is my understanding of "set -e". But perhaps I am
completely wrong and "set -e" is not thought to be used in a batch
which is more complex than what we know from DOS 3.
PS: English is not my native language, of course you already knew that
from my strange way to explain things.
--
-Tino
Valentin Hilbig; Am Sportfeld 5; 86482 Aystetten; Germany
Tel. +49 821 4865787; USt-IdNr. DE219734816
http://valentin.hilbig.de/
http://permalink.de/tino/impressum
|