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

后端 未结 10 1519
北荒
北荒 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:35

    If a subshell isn't an option (say you need to do something crazy like set a variable) then you can just check every single command that might fail and deal with it by appending || return $?. This causes the function to return the error code on failure.

    It's ugly, but it works

    #!/bin/sh
    set -e
    
    my_function() {
        echo "the following command could fail:"
        false || return $?
        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"
    

    gives

    the following command could fail:
    dealing with the problem
    run this all the time regardless of the success of my_function
    
    0 讨论(0)
  • 2020-11-28 06:38

    This is a bit of a kludge, but you can do:

    export -f f
    if sh -ec f; then 
    ...
    

    This will work if your shell supports export -f (bash does).

    Note that this will not terminate the script. The echo after the false in f will not execute, nor will the body of the if, but statements after the if will be executed.

    If you are using a shell that does not support export -f, you can get the semantics you want by running sh in the function:

    f() { sh -ec '
      echo This will execute
      false
      echo This will not
      '
    }
    
    0 讨论(0)
  • 2020-11-28 06:45

    This is by design and POSIX specification. We can read in man bash:

    If a compound command or shell function executes in a context where -e is being ignored, none of the commands executed within the compound command or function body will be affected by the -e setting, even if -e is set and a command returns a failure status. If a compound command or shell function sets -e while executing in a context where -e is ignored, that setting will not have any effect until the compound command or the command containing the function call completes.

    therefore you should avoid relying on set -e within functions.

    Given the following exampleAustin Group:

    set -e
    start() {
       some_server
       echo some_server started successfully
    }
    start || echo >&2 some_server failed
    

    the set -e is ignored within the function, because the function is a command in an AND-OR list other than the last.

    The above behaviour is specified and required by POSIX (see: Desired Action):

    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.

    0 讨论(0)
  • 2020-11-28 06:45

    I know this isn't what you asked, but you may or may not be aware that the behavior you seek is built into "make". Any part of a "make" process that fails aborts the run. It's a wholly different way of "programming", though, than shell scripting.

    0 讨论(0)
  • 2020-11-28 06:48

    I eventually went with this, which apparently works. I tried the export method at first, but then found that I needed to export every global (constant) variable the script uses.

    Disable set -e, then run the function call inside a subshell that has set -e enabled. Save the exit status of the subshell in a variable, re-enable set -e, then test the var.

    f() { echo "a"; false;  echo "Should NOT get HERE"; }
    
    # Don't pipe the subshell into anything or we won't be able to see its exit status
    set +e ; ( set -e; f ) ; err_status=$?
    set -e
    
    ## cleaner syntax which POSIX sh doesn't support.  Use bash/zsh/ksh/other fancy shells
    if ((err_status)) ; then
        echo "f returned false: $err_status"
    fi
    
    ## POSIX-sh features only (e.g. dash, /bin/sh)
    if test "$err_status" -ne 0 ; then
        echo "f returned false: $err_status"
    fi
    
    echo "always print this"
    

    You can't run f as part of a pipeline, or as part of a && of || command list (except as the last command in the pipe or list), or as the condition in an if or while, or other contexts that ignore set -e. This code also can't be in any of those contexts, so if you use this in a function, callers have to use the same subshell / save-exit-status trickery. This use of set -e for semantics similar to throwing/catching exceptions is not really suitable for general use, given the limitations and hard-to-read syntax.

    trap err_handler_function ERR has the same limitations as set -e, in that it won't fire for errors in contexts where set -e won't exit on failed commands.

    You might think the following would work, but it doesn't:

    if ! ( set -e; f );then    ##### doesn't work, f runs ignoring -e
        echo "f returned false: $?"
    fi
    

    set -e doesn't take effect inside the subshell because it remembers that it's inside the condition of an if. I thought being a subshell would change that, but only being in a separate file and running a whole separate shell on it would work.

    0 讨论(0)
  • 2020-11-28 06:49

    Join all commands in your function with the && operator. It's not too much trouble and will give the result you want.

    0 讨论(0)
提交回复
热议问题