Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

“set -e” in a function [duplicate]

set -e (or a script starting with #!/bin/sh -e) is extremely useful to automatically bomb out if there is a problem. It saves me having to error check every single command that might fail.

How do I get the equivalent of this inside a function?

For example, I have the following script that exits immediately on error with an error exit status:

#!/bin/sh -e

echo "the following command could fail:"
false
echo "this is after the command that fails"

The output is as expected:

the following command could fail:

Now I'd like to wrap this into a function:

#!/bin/sh -e

my_function() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

if ! my_function; then
    echo "dealing with the problem"
fi

echo "run this all the time regardless of the success of my_function"

Expected output:

the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function

Actual output:

the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function

(ie. the function is ignoring set -e)

This presumably is expected behaviour. My question is: how do I get the effect and usefulness of set -e inside a shell function? I'd like to be able to set something up such that I don't have to individually error check every call, but the script will stop on encountering an error. It should unwind the stack as far as is needed until I do check the result, or exit the script itself if I haven't checked it. This is what set -e does already, except it doesn't nest.

I've found the same question asked outside Stack Overflow but no suitable answer.

like image 479
Robie Basak Avatar asked Nov 01 '10 20:11

Robie Basak


1 Answers

Note/Edit: As a commenter pointed out, this answer uses bash, and not sh like the OP used in his question. I missed that detail when I originaly posted an answer. I will leave this answer up anyway since it might be interested to some passerby.

Y'aaaaaaaaaaaaaaaaaaallll ready for this?

Here's a way to do it with leveraging the DEBUG trap, which runs before each command, and sort of makes errors like the whole exception/try/catch idioms from other languages. Take a look. I've made your example one more 'call' deep.

#!/bin/bash

# Get rid of that disgusting set -e.  We don't need it anymore!
# functrace allows RETURN and DEBUG traps to be inherited by each
# subshell and function.  Plus, it doesn't suffer from that horrible
# erasure problem that -e and -E suffer from when the command 
# is used in a conditional expression.
set -o functrace

# A trap to bubble up the error unless our magic command is encountered 
# ('catch=$?' in this case) at which point it stops.  Also don't try to
# bubble the error if were not in a function.
trap '{ 
    code=$?
    if [[ $code != 0 ]] && [[ $BASH_COMMAND != '\''catch=$?'\'' ]]; then
        # If were in a function, return, else exit.
        [[ $FUNCNAME ]] && return $code || exit $code
    fi
}' DEBUG

my_function() {
    my_function2
}

my_function2() {
    echo "the following command could fail:"
    false
    echo "this is after the command that fails"
}

# the || isn't necessary, but the 'catch=$?' is.
my_function || catch=$?
echo "Dealing with the problem with errcode=$catch (⌐■_■)"

echo "run this all the time regardless of the success of my_function"

and the output:

the following command could fail:
Dealing with the problem with errcode=1 (⌐■_■)
run this all the time regardless of the success of my_function

I haven't tested this in the wild, but off the top of my head, there are a bunch of pros:

  1. It's actually not that slow. I've ran the script in a tight loop with and without the functrace option, and times are very close to each other under 10 000 iterations.

  2. You could expand on this DEBUG trap to print a stack trace without doing that whole looping over $FUNCNAME and $BASH_LINENO nonsense. You kinda get it for free (besides actually doing an echo line).

  3. Don't have to worry about that shopt -s inherit_errexit gotcha.

like image 172
jmrah Avatar answered Sep 21 '22 21:09

jmrah