Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [PATCH] More ERR_EXIT (was Re: Tests RE behavior of ERR_EXIT)



Hi Bart,

I just noticed the following change:

+Changes since 5.9
+-----------------
+
+Handling of ERR_EXIT is corrected when the final status of a structured
+command
(for, select, while, repeat, if, case, or a list in braces) is
+nonzero
.  To be compatible with other shells, "zsh -e" now exits in
+those circumstances, whereas previous versions did not.  This does not
+affect the handling of nonzero status within conditional statements.

This looks wrong to me. It's not compatible with the third exception of POSiX "set -e":

When this option is on, when any command fails (for any of the reasons listed in Consequences of Shell Errors or by returning an exit status greater than zero), the shell immediately shall exit, as if by executing the exit special built-in utility with no arguments, with the following exceptions:
  1. The failure of any individual command in a multi-command pipeline shall not cause the shell to exit. Only the failure of the pipeline itself shall be considered.
  2. 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.
  3. If the exit status of a compound command other than a subshell command was the result of a failure while -e was being ignored, then -e shall not apply to this command.
My high-level understanding of exception 3 is that if an error was produced by a condition (note that the last command in a sub-list is NOT a condition but a regular command), then that error should bubble up the evaluation stack, without triggering any ERR_EXIT, until it gets ignored (for example on the left of a ";" in a sequence) or it becomes the result of an enclosing function or subshell.

Let's consider the following example:

function foo() { if true; then false && true; fi }
function bar() { foo }
bar

Exception 2 tells us that "false && true" shouldn't trigger an ERR_EXIT. By exception 3, the resulting exit status becomes the result, first of the enclosing if, and then of the enclosing {}, without triggering an ERR_EXIT. It's only in function "bar" that an "ERR_EXIT" should be triggered because the call to "foo" returns 1 and function calls aren't compound commands.

Zsh 5.8.1, doesn't trigger any ERR_EXIT for this example. Zsh 5.9.*, with your latest changes, triggers an ERR_EXIT in "foo". Bash 5.1.16 triggers an ERR_EXIT in "bar". My understanding is that Bash is right.

Here is the script that I used to test this behavior with Zsh and Bash (for bash you have to comment out the "foo?3" tests):

# test.zsh
# Usage: test.zsh A1|...|A4|B1|...|B4|C1|...|C4
#
# Example: "test.zsh A1" runs the test for "fooA1".

set -e

function err-exit() {
    local error=$?;
    # Print where ERR_EXIT was called from.
    if [[ -n $ZSH_VERSION ]]; then
        local file=${funcfiletrace[1]%:*}
        local line=${funcfiletrace[1]#*:}
    elif [[ -n $BASH_VERSION ]]; then
        local caller=$(caller 0);
        local file=${caller##* }
        local line=${caller%% *}
    fi
    echo "Caught error $error at $file:$line: $(cat $file | head -$line | tail -1)" >&2
    return $error;
}

trap err-exit ERR
if [[ -n $ZSH_VERSION ]]; then
    alias init=''
elif [[ -n $BASH_VERSION ]]; then
    # It looks like in Bash the ERR trap must be set in every function
    shopt -s expand_aliases
    alias init='trap err-exit ERR'
fi

function fooA1() { init;               false        ;                               }
function fooA2() { init; if true; then false        ; fi                            }
function fooA3() { init;             { false        ; } always { true; }            }
function fooA4() { init;             { false        ; }                             }

function fooB1() { init;               false && true;                               }
function fooB2() { init; if true; then false && true; fi                            }
function fooB3() { init;             { false && true; } always { true; }            }
function fooB4() { init;             { false && true; }                             }

function fooC1() { init;               false && true                    ; echo foo; }
function fooC2() { init; if true; then false && true; fi                ; echo foo; }
function fooC3() { init;             { false && true; } always { true; }; echo foo; }
function fooC4() { init;             { false && true; }                 ; echo foo; }

function bar() {
    init
    echo Start
    foo$1
    echo End
}

bar "$@"

I included the "foo?3" tests because all the compound commands in loop.c seem to behave the same except for "exectry", which lacks the following line:

  this_noerrexit = (WC_SUBLIST_TYPE(*end) != WC_SUBLIST_END);

 I included the "foo?4" tests because sequences are not handled by one of the functions in loop.c but by some function in exec.c. However, all "foo?3" and "foo?4" tests nevertheless behave the same as the matching "foo?2" tests.

Here are the results:

        Zsh 5.8.1      Zsh 5.9.*      Bash 5.1.16 
 
fooA1   Exit in foo*   Exit in foo*   Exit in foo*
fooA2   Exit in foo*   Exit in foo*   Exit in foo* 
 
fooB1   Exit in bar    Exit in bar    Exit in bar
fooB2   No exit        Exit in foo*   Exit in bar 
 
fooC1   No exit        No exit        No exit
fooC2   No exit        Exit in foo*   No exit

My understanding is that Bash's behavior is the correct one.

Philippe



Messages sorted by: Reverse Date, Date, Thread, Author