On Wed, Aug 31, 2022 at 08:59:15AM +0200, Laszlo Ersek wrote: > > One of the simplest demonstrations on that page: > > > > $ bash -c 'set -e; f() { set -e; false; echo huh; }; f; echo survived' > > $ bash -c 'set -e; f() { set -e; false; echo huh; }; f && echo survived' > > huh > > survived > >
> > I agree this behavior is not intuitive. It does not seem to follow from > the POSIX language > > The -e setting shall be ignored when executing the compound list > following the while, until, if, or elif reserved word, a pipeline > beginning with the ! reserved word, or any command of an AND-OR list > other than the last. > > (which is the language I found "least distantly related" to this > behavior). Such "context rules" are frequent in other languages too, but > they only ever apply directly, and not recursively -- sub-contexts tend > to have their own separate environments. Actually, this is exactly the POSIX language that describes the behavior above. As soon as you invoke 'f && ...', f is now on the left-hand side of an AND-OR list, and the entire body of f is invoked in a scenario where you cannot re-enable 'set -e' no matter how hard you try. > > I've checked the subject script now and it does not seem to suffer from > this "set -e" pitfall; thus, I'm going to merge it. > > Now, whether this kills "set -e" for me for good... I'm not so sure. I'm > trying to think up a shell function that I would want to (a) call from > an outer conditional context, and at the same time (b) cause the whole > script to abort due to an internal error. > > I'm coming up empty here: those goals look mutually exclusive. Here's > why I think so: whether the exit status ("return value") of a function > matters or not is part of the function's specification; i.e., design. If > I design a function such that it return a meaningful value, I *already* > cannot allow any errors to go uncaught in the function body, and I > *also* cannot allow the function to kill the outer context due to any > internal problems. Conversely, if I only need a simple code extraction > from the outer, larger context, I will certainly rely on internal errors > in the function to abort the whole script -- but then I will have *zero > reason* to invoke the function from within an outer conditional. Yes, this is the counter-argument for why some people use 'set -e' in a shell script with functions, despite the potential for pitfall. The rule of thumb for such a script becomes: never invoke a function in a conditional if the function was not careful about handling errors without reliance on 'set -e'; or more generally, write all functions to assume that 'set -e' is a no-op. At which point, 'set -e' is only useful for the top-level code outside of functions; the more your script relies on functions instead of top-level code, the less likely 'set -e' is something you want to use. > > This is why I think that, although I've been using "set -e" for years > (decades?), I may not have written a single script plagued by this > particular misbehavior. Not because I'm that clever, but because (I > suspect) the situation demonstrated above "almost never" occurs in practice. > > So while I'm very surprised by the above demonstration, I'm quite > tempted to believe that the "set -e" masking behavior, albeit not > intuitive, is correct (and that at least I personally can continue using > "set -e", while keeping this non-intuitive behavior in mind). Whether it is "intuitive" or "correct" may be a matter of interpretation; but at the end of the day, POSIX standardized what existing practice does ('set -e' being disabled on the left side of && was historical practice even before shell functions were introduced), rather than what would be sane if the shell language were being developed from scratch. > > > Either way: what would be an alternative to "set -e" that: > > (1) scaled (in the sense that it does not mangle the whole script to > unreadability), > > (2) did not introduce the Arrow anti-pattern due to deeply nested "if"s > <https://blog.codinghorror.com/flattening-arrow-code/>, > <http://wiki.c2.com/?ArrowAntiPattern>? > > Would we have to write code like > > foo=$(somecommand ...) > ret=$? > if [ $ret -ne 0 ]; then > exit $ret > fi > some_other_command -- "$foo" > ret=$? > if [ $ret -ne 0 ]; then > exit $ret > fi You can trim it down to something more legible: foo=$(somecommand ...) || fatal some_other_command -- "$foo" || fatal where you write a helper function that encapulates the repetitive nature of invoking the command and checking for expected exit status. That particular style is how GNU coreutils writes much of its testsuite; picking a random example: https://git.sv.gnu.org/gitweb/?p=coreutils.git;a=blob;f=tests/ls/a-option.sh But yeah, converting a script that relied on 'set -e' to one that is equally safe without requires a framework of helper functions and a whole-script audit, which is not as scalable as designing that way from the outset. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org _______________________________________________ Libguestfs mailing list Libguestfs@redhat.com https://listman.redhat.com/mailman/listinfo/libguestfs