How do I get the effect and usefulness of “set -e” inside a shell function?

后端 未结 10 1870
北荒
北荒 2020-11-28 06:18

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 ch

10条回答
  •  长情又很酷
    2020-11-28 06:57

    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.

提交回复
热议问题