it-swarm-eu.dev

Fehlerbehandlung in Bash

Was ist Ihre Lieblingsmethode, um Fehler in Bash zu behandeln? Das beste Beispiel für den Umgang mit Fehlern, die ich im Web gefunden habe, wurde von William Shotts, Jr. unter http://www.linuxcommand.org geschrieben.

Er schlägt vor, die folgende Funktion zur Fehlerbehandlung in Bash zu verwenden:

#!/bin/bash

# A slicker error handling routine

# I put a variable in my scripts named PROGNAME which
# holds the name of the program being run.  You can get this
# value from the first item on the command line ($0).

# Reference: This was copied from <http://www.linuxcommand.org/wss0150.php>

PROGNAME=$(basename $0)

function error_exit
{

#   ----------------------------------------------------------------
#   Function for exit due to fatal program error
#       Accepts 1 argument:
#           string containing descriptive error message
#   ---------------------------------------------------------------- 

    echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
    exit 1
}

# Example call of the error_exit function.  Note the inclusion
# of the LINENO environment variable.  It contains the current
# line number.

echo "Example of error with line number and message"
error_exit "$LINENO: An error has occurred."

Haben Sie eine bessere Fehlerbehandlungsroutine, die Sie in Bash-Skripten verwenden?

215
Noob

Benutze eine Falle!

tempfiles=( )
cleanup() {
  rm -f "${tempfiles[@]}"
}
trap cleanup 0

error() {
  local parent_lineno="$1"
  local message="$2"
  local code="${3:-1}"
  if [[ -n "$message" ]] ; then
    echo "Error on or near line ${parent_lineno}: ${message}; exiting with status ${code}"
  else
    echo "Error on or near line ${parent_lineno}; exiting with status ${code}"
  fi
  exit "${code}"
}
trap 'error ${LINENO}' ERR

... dann, wann immer Sie eine temporäre Datei erstellen:

temp_foo="$(mktemp -t foobar.XXXXXX)"
tempfiles+=( "$temp_foo" )

und $temp_foo wird beim Beenden gelöscht und die aktuelle Zeilennummer gedruckt. (set -e führt ebenfalls zu einem Exit-on-Error-Verhalten, obwohl es schwerwiegende Einschränkungen enthält und schwächt die Vorhersehbarkeit und Portabilität des Codes.

Sie können den Trap entweder error für Sie aufrufen lassen (in diesem Fall wird der Standardexitcode 1 und keine Nachricht verwendet) oder ihn selbst aufrufen und explizite Werte bereitstellen. zum Beispiel:

error ${LINENO} "the foobar failed" 2

wird mit Status 2 beendet und gibt eine explizite Meldung aus.

148
Charles Duffy

Das ist eine gute Lösung. Ich wollte nur hinzufügen

set -e

als rudimentärer Fehlermechanismus. Es stoppt Ihr Skript sofort, wenn ein einfacher Befehl fehlschlägt. Ich denke, dies sollte das Standardverhalten sein: Da solche Fehler fast immer etwas Unerwartetes bedeuten, ist es nicht wirklich „vernünftig“, die folgenden Befehle weiter auszuführen.

115
Bruno De Fraine

Das Lesen aller Antworten auf dieser Seite hat mich sehr inspiriert.

Also, hier ist mein Hinweis:

Dateiinhalt: lib.trap.sh

lib_name='trap'
lib_version=20121026

stderr_log="/dev/shm/stderr.log"

#
# TO BE SOURCED ONLY ONCE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

if test "${g_libs[$lib_name]+_}"; then
    return 0
else
    if test ${#g_libs[@]} == 0; then
        declare -A g_libs
    fi
    g_libs[$lib_name]=$lib_version
fi


#
# MAIN CODE:
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
set -o nounset   ## set -u : exit the script if you try to use an uninitialised variable
set -o errexit   ## set -e : exit the script if any statement returns a non-true return value

exec 2>"$stderr_log"


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: EXIT_HANDLER
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function exit_handler ()
{
    local error_code="$?"

    test $error_code == 0 && return;

    #
    # LOCAL VARIABLES:
    # ------------------------------------------------------------------
    #    
    local i=0
    local regex=''
    local mem=''

    local error_file=''
    local error_lineno=''
    local error_message='unknown'

    local lineno=''


    #
    # PRINT THE HEADER:
    # ------------------------------------------------------------------
    #
    # Color the output if it's an interactive terminal
    test -t 1 && tput bold; tput setf 4                                 ## red bold
    echo -e "\n(!) EXIT HANDLER:\n"


    #
    # GETTING LAST ERROR OCCURRED:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    #
    # Read last file from the error log
    # ------------------------------------------------------------------
    #
    if test -f "$stderr_log"
        then
            stderr=$( tail -n 1 "$stderr_log" )
            rm "$stderr_log"
    fi

    #
    # Managing the line to extract information:
    # ------------------------------------------------------------------
    #

    if test -n "$stderr"
        then        
            # Exploding stderr on :
            mem="$IFS"
            local shrunk_stderr=$( echo "$stderr" | sed 's/\: /\:/g' )
            IFS=':'
            local stderr_parts=( $shrunk_stderr )
            IFS="$mem"

            # Storing information on the error
            error_file="${stderr_parts[0]}"
            error_lineno="${stderr_parts[1]}"
            error_message=""

            for (( i = 3; i <= ${#stderr_parts[@]}; i++ ))
                do
                    error_message="$error_message "${stderr_parts[$i-1]}": "
            done

            # Removing last ':' (colon character)
            error_message="${error_message%:*}"

            # Trim
            error_message="$( echo "$error_message" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
    fi

    #
    # GETTING BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #
    _backtrace=$( backtrace 2 )


    #
    # MANAGING THE OUTPUT:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    local lineno=""
    regex='^([a-z]{1,}) ([0-9]{1,})$'

    if [[ $error_lineno =~ $regex ]]

        # The error line was found on the log
        # (e.g. type 'ff' without quotes wherever)
        # --------------------------------------------------------------
        then
            local row="${BASH_REMATCH[1]}"
            lineno="${BASH_REMATCH[2]}"

            echo -e "FILE:\t\t${error_file}"
            echo -e "${row^^}:\t\t${lineno}\n"

            echo -e "ERROR CODE:\t${error_code}"             
            test -t 1 && tput setf 6                                    ## white yellow
            echo -e "ERROR MESSAGE:\n$error_message"


        else
            regex="^${error_file}\$|^${error_file}\s+|\s+${error_file}\s+|\s+${error_file}\$"
            if [[ "$_backtrace" =~ $regex ]]

                # The file was found on the log but not the error line
                # (could not reproduce this case so far)
                # ------------------------------------------------------
                then
                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    echo -e "ERROR MESSAGE:\n${stderr}"

                # Neither the error line nor the error file was found on the log
                # (e.g. type 'cp ffd fdf' without quotes wherever)
                # ------------------------------------------------------
                else
                    #
                    # The error file is the first on backtrace list:

                    # Exploding backtrace on newlines
                    mem=$IFS
                    IFS='
                    '
                    #
                    # Substring: I keep only the carriage return
                    # (others needed only for tabbing purpose)
                    IFS=${IFS:0:1}
                    local lines=( $_backtrace )

                    IFS=$mem

                    error_file=""

                    if test -n "${lines[1]}"
                        then
                            array=( ${lines[1]} )

                            for (( i=2; i<${#array[@]}; i++ ))
                                do
                                    error_file="$error_file ${array[$i]}"
                            done

                            # Trim
                            error_file="$( echo "$error_file" | sed -e 's/^[ \t]*//' | sed -e 's/[ \t]*$//' )"
                    fi

                    echo -e "FILE:\t\t$error_file"
                    echo -e "ROW:\t\tunknown\n"

                    echo -e "ERROR CODE:\t${error_code}"
                    test -t 1 && tput setf 6                            ## white yellow
                    if test -n "${stderr}"
                        then
                            echo -e "ERROR MESSAGE:\n${stderr}"
                        else
                            echo -e "ERROR MESSAGE:\n${error_message}"
                    fi
            fi
    fi

    #
    # PRINTING THE BACKTRACE:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 7                                            ## white bold
    echo -e "\n$_backtrace\n"

    #
    # EXITING:
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #

    test -t 1 && tput setf 4                                            ## red bold
    echo "Exiting!"

    test -t 1 && tput sgr0 # Reset terminal

    exit "$error_code"
}
trap exit_handler EXIT                                                  # ! ! ! TRAP EXIT ! ! !
trap exit ERR                                                           # ! ! ! TRAP ERR ! ! !


###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##
#
# FUNCTION: BACKTRACE
#
###~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~##

function backtrace
{
    local _start_from_=0

    local params=( "[email protected]" )
    if (( "${#params[@]}" >= "1" ))
        then
            _start_from_="$1"
    fi

    local i=0
    local first=false
    while caller $i > /dev/null
    do
        if test -n "$_start_from_" && (( "$i" + 1   >= "$_start_from_" ))
            then
                if test "$first" == false
                    then
                        echo "BACKTRACE IS:"
                        first=true
                fi
                caller $i
        fi
        let "i=i+1"
    done
}

return 0



Anwendungsbeispiel:
Dateiinhalt: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

echo "doing something wrong now .."
echo "$foo"

exit 0


Laufen:

bash trap-test.sh

Ausgabe:

doing something wrong now ..

(!) EXIT HANDLER:

FILE:       trap-test.sh
LINE:       6

ERROR CODE: 1
ERROR MESSAGE:
foo:   unassigned variable

BACKTRACE IS:
1 main trap-test.sh

Exiting!


Wie Sie dem folgenden Screenshot entnehmen können, ist die Ausgabe farbig und die Fehlermeldung in der verwendeten Sprache.

enter image description here

73
Luca Borrione

Eine äquivalente Alternative zu "set -e" ist

set -o errexit

Dadurch wird die Bedeutung des Flags etwas klarer als nur "-e".

Zufälliges Hinzufügen: Um das Flag vorübergehend zu deaktivieren und zur Standardeinstellung (Fortsetzung der Ausführung unabhängig von den Beendigungscodes) zurückzukehren, verwenden Sie einfach

set +e
echo "commands run here returning non-zero exit codes will not cause the entire script to fail"
echo "false returns 1 as an exit code"
false
set -e

Dies schließt eine ordnungsgemäße Fehlerbehandlung aus, die in anderen Antworten erwähnt wird, ist jedoch schnell und effektiv (genau wie bash).

22
Ben Scholbrock

Inspiriert von den hier vorgestellten Ideen, habe ich in meinem bash boilerplate project eine lesbare und bequeme Methode entwickelt, um Fehler in Bash-Skripten zu behandeln.

Durch einfaches Aufrufen der Bibliothek erhalten Sie die folgenden Informationen (dh, die Ausführung wird angehalten, wenn ein Fehler auftritt, als würde set -e Verwendet, und zwar dank eines trap auf ERR und einige bash-fu):

bash-oo-framework error handling

Es gibt einige zusätzliche Funktionen, die bei der Behandlung von Fehlern helfen, z. B. try and catch oder den throw Schlüsselwort, mit dem Sie die Ausführung an einem Punkt unterbrechen können, um die Rückverfolgung zu sehen. Wenn das Terminal dies unterstützt, werden außerdem Powerline-Emojis ausgespuckt, Teile der Ausgabe farblich hervorgehoben und die Methode unterstrichen, die die Ausnahme im Kontext der Codezeile verursacht hat.

Der Nachteil ist - es ist nicht portierbar -, dass der Code in bash funktioniert, wahrscheinlich nur> = 4 (aber ich könnte mir vorstellen, dass er mit etwas Aufwand in bash 3 portiert werden könnte).

Der Code ist zur besseren Handhabung in mehrere Dateien unterteilt, aber ich war von der Backtrace-Idee von die Antwort oben von Luca Borrione inspiriert.

Um mehr zu lesen oder einen Blick auf die Quelle zu werfen, siehe GitHub:

https://github.com/niieani/bash-oo-framework#error-handling-with-exceptions-and-throw

19
niieani

Ich bevorzuge etwas wirklich Leichtes anzurufen. Also benutze ich etwas, das etwas kompliziert aussieht, aber einfach zu bedienen ist. Normalerweise kopiere ich einfach den folgenden Code und füge ihn in meine Skripte ein. Eine Erklärung folgt dem Code.

#This function is used to cleanly exit any script. It does this displaying a
# given error message, and exiting with an error code.
function error_exit {
    echo
    echo "[email protected]"
    exit 1
}
#Trap the killer signals so that we can exit with a good message.
trap "error_exit 'Received signal SIGHUP'" SIGHUP
trap "error_exit 'Received signal SIGINT'" SIGINT
trap "error_exit 'Received signal SIGTERM'" SIGTERM

#Alias the function so that it will print a message with the following format:
#prog-name(@line#): message
#We have to explicitly allow aliases, we do this because they make calling the
#function much easier (see example).
shopt -s expand_aliases
alias die='error_exit "Error ${0}(@`echo $(( $LINENO - 1 ))`):"'

Normalerweise rufe ich die Bereinigungsfunktion neben der error_exit-Funktion auf, aber das ist von Skript zu Skript unterschiedlich, sodass ich es weggelassen habe. Die Fallen fangen die gemeinsamen Abschlusssignale auf und sorgen dafür, dass alles aufgeräumt wird. Der Alias ​​ist das, was die wahre Magie ausmacht. Ich überprüfe gerne alles auf Fehler. Im Allgemeinen rufe ich Programme in einem "Wenn!" Typ Anweisung. Durch Subtrahieren von 1 von der Zeilennummer teilt mir der Alias ​​mit, wo der Fehler aufgetreten ist. Es ist auch kinderleicht anzurufen und ziemlich idiotensicher. Im Folgenden finden Sie ein Beispiel (ersetzen Sie einfach/bin/false durch das, was Sie aufrufen möchten).

#This is an example useage, it will print out
#Error prog-name (@1): Who knew false is false.
if ! /bin/false ; then
    die "Who knew false is false."
fi
11
Michael Nooner

Eine weitere Überlegung ist der zurückzugebende Exit-Code. Gerade "1 "ist ein ziemlich normaler Standard, obwohl es eine Handvoll reservierter Exit-Codes gibt, die die Bash selbst verwendet , und dieselbe Seite argumentiert, dass benutzerdefinierte Codes verwendet werden sollten im Bereich 64-113 liegen, um den C/C++ - Standards zu entsprechen.

Sie können auch den Bitvektor-Ansatz in Betracht ziehen, den mount für seine Exit-Codes verwendet:

 0  success
 1  incorrect invocation or permissions
 2  system error (out of memory, cannot fork, no more loop devices)
 4  internal mount bug or missing nfs support in mount
 8  user interrupt
16  problems writing or locking /etc/mtab
32  mount failure
64  some mount succeeded

OR- Wenn Sie die Codes gemeinsam eingeben, kann Ihr Skript mehrere gleichzeitige Fehler melden.

6
yukondude

Ich benutze den folgenden Trap-Code, er erlaubt auch Fehler können durch Pipes und 'time'-Befehle verfolgt werden

#!/bin/bash
set -o pipefail  # trace ERR through pipes
set -o errtrace  # trace ERR through 'time command' and other functions
function error() {
    JOB="$0"              # job name
    LASTLINE="$1"         # line of error occurrence
    LASTERR="$2"          # error code
    echo "ERROR in ${JOB} : line ${LASTLINE} with exit code ${LASTERR}"
    exit 1
}
trap 'error ${LINENO} ${?}' ERR
4
Olivier Delrieu

Ich habe benutzt

die() {
        echo $1
        kill $$
}

vor; Ich denke, weil 'exit' für mich aus irgendeinem Grund fehlgeschlagen ist. Die obigen Standardeinstellungen scheinen jedoch eine gute Idee zu sein.

3
pjz

This hat mir jetzt eine Weile gut gedient. Es druckt Fehler- oder Warnmeldungen in rot, eine Zeile pro Parameter, und lässt einen optionalen Beendigungscode zu.

# Custom errors
EX_UNKNOWN=1

warning()
{
    # Output warning messages
    # Color the output red if it's an interactive terminal
    # @param $1...: Messages

    test -t 1 && tput setf 4

    printf '%s\n' "[email protected]" >&2

    test -t 1 && tput sgr0 # Reset terminal
    true
}

error()
{
    # Output error messages with optional exit code
    # @param $1...: Messages
    # @param $N: Exit code (optional)

    messages=( "[email protected]" )

    # If the last parameter is a number, it's not part of the messages
    last_parameter="${messages[@]: -1}"
    if [[ "$last_parameter" =~ ^[0-9]*$ ]]
    then
        exit_code=$last_parameter
        unset messages[$((${#messages[@]} - 1))]
    fi

    warning "${messages[@]}"

    exit ${exit_code:-$EX_UNKNOWN}
}
3
l0b0

Ich bin mir nicht sicher, ob dies für Sie hilfreich sein wird, habe jedoch einige der hier vorgeschlagenen Funktionen geändert, um die Prüfung auf Fehler (Exit-Code vom vorherigen Befehl) darin aufzunehmen. Bei jeder "Prüfung" übergebe ich auch als Parameter die "Meldung", was der Fehler zu Protokollierungszwecken ist.

#!/bin/bash

error_exit()
{
    if [ "$?" != "0" ]; then
        log.sh "$1"
        exit 1
    fi
}

Um es jetzt im selben Skript aufzurufen (oder in einem anderen, wenn ich export -f error_exit Verwende), schreibe ich einfach den Namen der Funktion und übergebe eine Nachricht als Parameter, wie folgt:

#!/bin/bash

cd /home/myuser/afolder
error_exit "Unable to switch to folder"

rm *
error_exit "Unable to delete all files"

Auf diese Weise konnte ich eine wirklich robuste Bash-Datei für einen automatisierten Prozess erstellen, die bei Fehlern stoppt und mich benachrichtigt (log.sh Wird dies tun).

2

Diese Funktion hat mir in letzter Zeit recht gute Dienste geleistet:

action () {
    # Test if the first parameter is non-zero
    # and return straight away if so
    if test $1 -ne 0
    then
        return $1
    fi

    # Discard the control parameter
    # and execute the rest
    shift 1
    "[email protected]"
    local status=$?

    # Test the exit status of the command run
    # and display an error message on failure
    if test ${status} -ne 0
    then
        echo Command \""[email protected]"\" failed >&2
    fi

    return ${status}
}

Sie rufen es auf, indem Sie 0 oder den letzten Rückgabewert an den Namen des auszuführenden Befehls anhängen, damit Sie Befehle verketten können, ohne nach Fehlerwerten suchen zu müssen. Mit diesem Anweisungsblock:

command1 param1 param2 param3...
command2 param1 param2 param3...
command3 param1 param2 param3...
command4 param1 param2 param3...
command5 param1 param2 param3...
command6 param1 param2 param3...

Wird dies:

action 0 command1 param1 param2 param3...
action $? command2 param1 param2 param3...
action $? command3 param1 param2 param3...
action $? command4 param1 param2 param3...
action $? command5 param1 param2 param3...
action $? command6 param1 param2 param3...

<<<Error-handling code here>>>

Wenn einer der Befehle fehlschlägt, wird der Fehlercode einfach an das Ende des Blocks übergeben. Ich finde es nützlich, wenn Sie nicht möchten, dass nachfolgende Befehle ausgeführt werden, wenn ein früherer fehlschlägt, aber Sie möchten auch nicht, dass das Skript sofort beendet wird (z. B. innerhalb einer Schleife).

1
xarxziux

Dieser Trick ist nützlich, wenn Befehle oder Funktionen fehlen. Der Name der fehlenden Funktion (oder der ausführbaren Datei) wird in $ _ übergeben.

function handle_error {
    status=$?
    last_call=$1

    # 127 is 'command not found'
    (( status != 127 )) && return

    echo "you tried to call $last_call"
    return
}

# Trap errors.
trap 'handle_error "$_"' ERR
0
Orwellophile

Die Verwendung einer Falle ist nicht immer eine Option. Wenn Sie beispielsweise eine wiederverwendbare Funktion schreiben, die eine Fehlerbehandlung erfordert und von einem beliebigen Skript aus aufgerufen werden kann (nachdem die Datei mit Hilfsfunktionen beschafft wurde), kann diese Funktion keine Aussage über die Beendigungszeit des äußeren Skripts treffen. Das macht die Verwendung von Fallen sehr schwierig. Ein weiterer Nachteil bei der Verwendung von Traps ist die schlechte Kompositionsfähigkeit, da Sie die Gefahr haben, vorherige Traps zu überschreiben, die möglicherweise früher in der Aufruferkette eingerichtet wurden.

Es gibt einen kleinen Trick, der verwendet werden kann, um eine ordnungsgemäße Fehlerbehandlung ohne Fallen durchzuführen. Wie Sie vielleicht bereits aus anderen Antworten wissen, funktioniert set -e In Befehlen nicht, wenn Sie den Operator || Nach ihnen verwenden, auch wenn Sie sie in einer Subshell ausführen. Dies würde beispielsweise nicht funktionieren:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_1.sh: line 16: some_failed_command: command not found
# <-- inner
# <-- outer

set -e

outer() {
  echo '--> outer'
  (inner) || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

Der Operator || Wird jedoch benötigt, um die Rückkehr von der äußeren Funktion vor der Bereinigung zu verhindern. Der Trick besteht darin, den inneren Befehl im Hintergrund auszuführen und dann sofort darauf zu warten. Das eingebaute wait gibt den Exit-Code des inneren Befehls zurück, und jetzt verwenden Sie || Nach wait, nicht die innere Funktion, also set -e Funktioniert ordnungsgemäß in letzterem:

#!/bin/sh

# prints:
#
# --> outer
# --> inner
# ./so_2.sh: line 27: some_failed_command: command not found
# --> cleanup

set -e

outer() {
  echo '--> outer'
  inner &
  wait $! || {
    exit_code=$?
    echo '--> cleanup'
    return $exit_code
  }
  echo '<-- outer'
}

inner() {
  set -e
  echo '--> inner'
  some_failed_command
  echo '<-- inner'
}

outer

Hier ist die generische Funktion, die auf dieser Idee aufbaut. Es sollte in allen POSIX-kompatiblen Shells funktionieren, wenn Sie local Keywords entfernen, d. H. Alle local x=y Durch nur x=y Ersetzen:

# [CLEANUP=cleanup_cmd] run cmd [args...]
#
# `cmd` and `args...` A command to run and its arguments.
#
# `cleanup_cmd` A command that is called after cmd has exited,
# and gets passed the same arguments as cmd. Additionally, the
# following environment variables are available to that command:
#
# - `RUN_CMD` contains the `cmd` that was passed to `run`;
# - `RUN_EXIT_CODE` contains the exit code of the command.
#
# If `cleanup_cmd` is set, `run` will return the exit code of that
# command. Otherwise, it will return the exit code of `cmd`.
#
run() {
  local cmd="$1"; shift
  local exit_code=0

  local e_was_set=1; if ! is_Shell_attribute_set e; then
    set -e
    e_was_set=0
  fi

  "$cmd" "[email protected]" &

  wait $! || {
    exit_code=$?
  }

  if [ "$e_was_set" = 0 ] && is_Shell_attribute_set e; then
    set +e
  fi

  if [ -n "$CLEANUP" ]; then
    RUN_CMD="$cmd" RUN_EXIT_CODE="$exit_code" "$CLEANUP" "[email protected]"
    return $?
  fi

  return $exit_code
}


is_Shell_attribute_set() { # attribute, like "x"
  case "$-" in
    *"$1"*) return 0 ;;
    *)    return 1 ;;
  esac
}

Anwendungsbeispiel:

#!/bin/sh
set -e

# Source the file with the definition of `run` (previous code snippet).
# Alternatively, you may paste that code directly here and comment the next line.
. ./utils.sh


main() {
  echo "--> main: [email protected]"
  CLEANUP=cleanup run inner "[email protected]"
  echo "<-- main"
}


inner() {
  echo "--> inner: [email protected]"
  sleep 0.5; if [ "$1" = 'fail' ]; then
    oh_my_god_look_at_this
  fi
  echo "<-- inner"
}


cleanup() {
  echo "--> cleanup: [email protected]"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "[email protected]"

Beispiel ausführen:

$ ./so_3 fail; echo "exit code: $?"

--> main: fail
--> inner: fail
./so_3: line 15: oh_my_god_look_at_this: command not found
--> cleanup: fail
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 127
<-- cleanup
exit code: 127

$ ./so_3 pass; echo "exit code: $?"

--> main: pass
--> inner: pass
<-- inner
--> cleanup: pass
    RUN_CMD = 'inner'
    RUN_EXIT_CODE = 0
<-- cleanup
<-- main
exit code: 0

Das einzige, was Sie beachten müssen, wenn Sie diese Methode verwenden, ist, dass alle Änderungen an Shell-Variablen, die Sie von dem an run übergebenen Befehl ausführen, nicht an die aufrufende Funktion weitergegeben werden, da der Befehl in einer Subshell ausgeführt wird.

0
skozin