Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash fail fast functions

Tags:

bash

There are recommendations to use the following options to make Bash fail fast:

set -o errexit
set -o nounset
set -o pipefail

These options however do not work as expected for Bash functions, piped via ||.

E.g. in script

#!/bin/bash

set -o errexit
set -o nounset
set -o pipefail

my_function() {
    test -z "$1"
    echo "this SHOULD NOT be printed"
}

my_function 1 || echo "???" # 1

my_function 1 # 2

echo "this will not be printed"

Line # 2 will cause the script to terminate with code 1 without any output. This is what I expect.

Line # 1 confuses me actually: my_function will successfully be completed, printing "this SHOULD NOT be printed" and returning code 0, thus the "???" will not be printed.

How can I make Bash to process my_function on line # 1 the same fail fast way, as on line # 2?

like image 706
Aleksandr Murashov Avatar asked Feb 05 '23 11:02

Aleksandr Murashov


2 Answers

There are also better recommendations not to use set -e/errexit:

Clearly we don't want to abort when the conditional (in if [ -d /foo ]; then), returns non-zero. [...] The implementors decided to make a bunch of special rules, like "commands that are part of an if test are immune", or "commands in a pipeline, other than the last one, are immune".

These rules are extremely convoluted, and they still fail to catch even some remarkably simple cases. Even worse, the rules change from one Bash version to another, as Bash attempts to track the extremely slippery POSIX definition of this "feature". When a SubShell is involved, it gets worse still -- the behavior changes depending on whether Bash is invoked in POSIX mode.

If the script were to stop on a non-zero return, foo || bar would also be useless because bar would never run (the script would instead exit). Therefore, one of these convoluted rules is that non-zero returns in commands on the left-hand side of && or || will not cause the script to exit. This is the effect you're seeing.

There is no way to make set -e work properly and there is no automatic replacement. You can not make it work the way you want without writing manual error handling.

like image 71
that other guy Avatar answered Feb 08 '23 16:02

that other guy


Looking at the bash manual, here is what is says for the trap command:

trap [-lp] [arg] [sigspec …]

If a sigspec is ERR, the command arg is executed whenever a pipeline (which may consist of a single simple command), a list, or a compound command returns a non-zero exit status, subject to the following conditions. The ERR trap is not executed if the failed command is part of the command list immediately following an until or while keyword, part of the test following the if or elif reserved words, part of a command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command’s return status is being inverted using !. These are the same conditions obeyed by the errexit (-e) option.

Look at the last sentence especially. That explains it!

like image 32
codeforester Avatar answered Feb 08 '23 15:02

codeforester