web-dev-qa-db-fra.com

Traitement des erreurs dans Bash

Quelle est votre méthode préférée pour gérer les erreurs dans Bash? Le meilleur exemple d’erreurs de traitement que j’ai trouvé sur le Web a été écrit par William Shotts, Jr sur http://www.linuxcommand.org .

Il suggère d'utiliser la fonction suivante pour la gestion des erreurs dans Bash:

#!/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."

Avez-vous une meilleure routine de traitement des erreurs que vous utilisez dans les scripts Bash?

215
Noob

Utilisez un piège!

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

... alors, chaque fois que vous créez un fichier temporaire:

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

et $temp_foo sera supprimé à la sortie et le numéro de ligne actuel sera imprimé. (set -e vous donnera également un comportement de sortie sur erreur, même s'il comporte de sérieuses mises en garde et affaiblit la prévisibilité et la portabilité du code).

Vous pouvez soit laisser l'appel de trappe appeler error pour vous (dans ce cas, il utilise le code de sortie par défaut de 1 et aucun message) ou l'appeler vous-même et fournir des valeurs explicites; par exemple:

error ${LINENO} "the foobar failed" 2

quittera avec le statut 2 et donnera un message explicite.

148
Charles Duffy

C'est une bonne solution. Je voulais juste ajouter

set -e

comme un mécanisme d'erreur rudimentaire. Votre script sera immédiatement arrêté si une simple commande échoue. Je pense que cela aurait dû être le comportement par défaut: étant donné que de telles erreurs signifient presque toujours quelque chose d'inattendu, il n'est pas vraiment 'sain' de continuer à exécuter les commandes suivantes.

115
Bruno De Fraine

La lecture de toutes les réponses sur cette page m'a beaucoup inspiré.

Alors, voici mon indice:

contenu du fichier: 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=( "$@" )
    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



Exemple d'utilisation:
contenu du fichier: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

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

exit 0


Fonctionnement:

bash trap-test.sh

Sortie:

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!


Comme vous pouvez le voir sur la capture d'écran ci-dessous, la sortie est colorée et le message d'erreur s'affiche dans la langue utilisée.

enter image description here

73
Luca Borrione

Une alternative équivalente à "set -e" est

set -o errexit

Cela rend le sens du drapeau un peu plus clair que juste "-e".

Ajout aléatoire: pour désactiver temporairement l'indicateur et revenir à la valeur par défaut (exécution continue quels que soient les codes de sortie), utilisez simplement

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

Cela empêche le traitement correct des erreurs mentionné dans d'autres réponses, mais est rapide et efficace (tout comme bash).

22
Ben Scholbrock

Inspiré par les idées présentées ici, j'ai développé un moyen lisible et pratique pour gérer les erreurs dans les scripts bash de mon projet bash warmplate .

En recherchant simplement la bibliothèque, vous obtenez immédiatement ce qui suit (c’est-à-dire que cela arrêtera toute exécution, comme si vous utilisiez set -e grâce à un trap sur ERR et à certains bash-fu ):

bash-oo-framework error handling

Certaines fonctionnalités supplémentaires aident à gérer les erreurs, telles que essayer d'attraper , ou le lancer mot-clé, qui vous permet d'interrompre l'exécution à un moment donné pour voir la trace. De plus, si le terminal le prend en charge, il émet des émoticônes Powerline, colorie des parties de la sortie pour une meilleure lisibilité et souligne la méthode qui a provoqué l’exception dans le contexte de la ligne de code.

L'inconvénient est - ce n'est pas portable - le code fonctionne en bash, probablement> = 4 seulement (mais j'imagine qu'il pourrait être porté avec un peu d'effort pour bash 3).

Le code est séparé en plusieurs fichiers pour une meilleure gestion, mais l’idée du backtrace de la réponse ci-dessus de Luca Borrione a été inspirée.

Pour en savoir plus ou consulter la source, voir GitHub:

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

19
niieani

Je préfère quelque chose de vraiment facile à appeler. J'utilise donc quelque chose qui semble un peu compliqué, mais qui est facile à utiliser. En général, je ne fais que copier-coller le code ci-dessous dans mes scripts. Une explication suit le 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 "$@"
    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 ))`):"'

Je mets généralement un appel à la fonction de nettoyage à côté de la fonction error_exit, mais cela varie d'un script à l'autre, je l'ai donc laissé de côté. Les pièges captent les signaux de terminaison communs et veillent à ce que tout soit nettoyé. Le pseudonyme est ce qui fait la vraie magie. J'aime tout vérifier pour les échecs. Donc, en général, j'appelle des programmes dans un "if!" déclaration de type. En soustrayant 1 du numéro de ligne, le pseudonyme me dira où la défaillance s'est produite. Il est également extrêmement simple d’appeler et pratiquement idiot. Vous trouverez ci-dessous un exemple (remplacez simplement/bin/false par ce que vous allez appeler).

#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

Une autre considération est le code de sortie à retourner. Juste "1" est assez standard, bien qu'il existe une poignée de codes de sortie réservés que bash utilise lui-même , et cette même page indique que les codes définis par l'utilisateur doivent être compris dans la plage 64- 113 pour se conformer aux normes C/C++.

Vous pouvez également considérer l'approche de vecteur de bits que mount utilise pour ses codes de sortie:

 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- la combinaison des codes permet à votre script de signaler plusieurs erreurs simultanément.

6
yukondude

J'utilise le code d'interruption suivant, il permet également les erreurs doivent être suivies à l'aide de pipes et de commandes 'time'

#!/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

J'ai utilisé

die() {
        echo $1
        kill $$
}

avant; Je pense que parce que 'sortie' échouait pour moi pour une raison quelconque. Les valeurs par défaut ci-dessus semblent être une bonne idée, cependant.

3
pjz

This m'a bien servi pendant un certain temps maintenant. Il imprime les messages d'erreur ou d'avertissement en rouge, une ligne par paramètre, et permet un code de sortie facultatif.

# 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' "$@" >&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=( "$@" )

    # 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

Je ne sais pas si cela vous sera utile, mais j’ai modifié certaines des fonctions suggérées ici afin d’y inclure le contrôle de l’erreur (code de sortie de la commande précédente). A chaque "vérification", je passe également en paramètre le "message" de ce que l'erreur est à des fins de journalisation.

#!/bin/bash

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

Maintenant, pour l'appeler dans le même script (ou dans un autre si j'utilise export -f error_exit), j'écris simplement le nom de la fonction et passe un message en paramètre, comme ceci:

#!/bin/bash

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

rm *
error_exit "Unable to delete all files"

En utilisant ceci, j'ai pu créer un fichier bash vraiment robuste pour un processus automatisé et il s'arrêtera en cas d'erreur et m'en avertira (log.sh le fera).

2
Nelson Rodriguez

Cette fonction m'a bien servi récemment:

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
    "$@"
    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 \""$@"\" failed >&2
    fi

    return ${status}
}

Vous l'appelez en ajoutant 0 ou la dernière valeur renvoyée au nom de la commande à exécuter afin de pouvoir chaîner les commandes sans avoir à vérifier les valeurs d'erreur. Avec cela, cette instruction bloque:

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

Devient ceci:

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>>>

Si l'une des commandes échoue, le code d'erreur est simplement passé à la fin du bloc. Je trouve cela utile lorsque vous ne voulez pas que les commandes suivantes soient exécutées en cas d'échec d'une précédente, mais vous ne voulez pas non plus que le script se ferme immédiatement (par exemple, dans une boucle).

1
xarxziux

Cette astuce est utile pour les commandes ou fonctions manquantes. Le nom de la fonction manquante (ou de l'exécutable) sera passé dans $ _

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

L'utilisation de trap n'est pas toujours une option. Par exemple, si vous écrivez une fonction réutilisable nécessitant une gestion des erreurs et pouvant être appelée à partir de n’importe quel script (après avoir recherché le fichier avec des fonctions d’aide), cette fonction ne peut en aucun cas tenir compte du temps de sortie du script externe. ce qui rend l'utilisation des pièges très difficile. L'utilisation de pièges est un autre inconvénient, c'est une mauvaise composition, dans la mesure où vous risquez d'écraser les pièges précédents, qui pourraient être définis plus tôt dans la chaîne des appelants.

Il existe un petit truc qui peut être utilisé pour gérer correctement les erreurs sans pièges. Comme vous le savez peut-être déjà dans d'autres réponses, _set -e_ ne fonctionne pas dans les commandes si vous utilisez l'opérateur _||_ après celles-ci, même si vous les exécutez dans un sous-shell; Par exemple, cela ne fonctionnerait pas:

_#!/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
_

Mais l'opérateur _||_ est nécessaire pour éviter de revenir de la fonction externe avant le nettoyage. L'astuce consiste à exécuter la commande interne en arrière-plan, puis à l'attendre immédiatement. La fonction intégrée wait renverra le code de sortie de la commande interne. Désormais, vous utilisez _||_ après wait, et non la fonction interne. _set -e_ fonctionne correctement à l'intérieur du dernier:

_#!/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
_

Voici la fonction générique qui s'appuie sur cette idée. Cela devrait fonctionner dans tous les shells compatibles POSIX si vous supprimez les mots-clés local, c'est-à-dire que vous remplacez tous les _local x=y_ par juste _x=y_:

_# [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" "$@" &

  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" "$@"
    return $?
  fi

  return $exit_code
}


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

Exemple d'utilisation:

_#!/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: $@"
  CLEANUP=cleanup run inner "$@"
  echo "<-- main"
}


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


cleanup() {
  echo "--> cleanup: $@"
  echo "    RUN_CMD = '$RUN_CMD'"
  echo "    RUN_EXIT_CODE = $RUN_EXIT_CODE"
  sleep 0.3
  echo '<-- cleanup'
  return $RUN_EXIT_CODE
}

main "$@"
_

Lancer l'exemple:

_$ ./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
_

La seule chose dont vous devez être conscient lorsque vous utilisez cette méthode est que toutes les modifications de variables Shell effectuées à partir de la commande transmise à run ne seront pas propagées à la fonction appelante, car la commande s'exécute dans un sous-shell.

0
skozin