it-swarm.it

Gestione degli errori in Bash

Qual è il tuo metodo preferito per gestire gli errori in Bash? Il miglior esempio di gestione degli errori che ho trovato sul web è stato scritto da William Shotts, Jr at http://www.linuxcommand.org .

Suggerisce di utilizzare la seguente funzione per la gestione degli errori in 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."

Hai una migliore gestione degli errori che usi negli script di Bash?

211
Noob

Usa una trappola!

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

... quindi, ogni volta che crei un file temporaneo:

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

e $temp_foo verrà eliminato all'uscita e verrà stampato il numero di riga corrente. (Anche set -e ti darà il comportamento di exit-on-error, anche se viene fornito con avvertimenti importanti e indebolisce la prevedibilità e la portabilità del codice).

Puoi consentire al trap di chiamare error per te (nel qual caso utilizza il codice di uscita predefinito di 1 e nessun messaggio) o chiamarlo tu stesso e fornire valori espliciti; per esempio:

error ${LINENO} "the foobar failed" 2

uscirà con lo stato 2 e darà un messaggio esplicito.

145
Charles Duffy

Questa è una soluzione eccellente. Volevo solo aggiungere

set -e

come un meccanismo di errore rudimentale. Interrompe immediatamente il tuo script se un semplice comando fallisce. Penso che questo dovrebbe essere il comportamento predefinito: dal momento che tali errori indicano quasi sempre qualcosa di inaspettato, non è proprio 'sano' continuare a eseguire i seguenti comandi.

112
Bruno De Fraine

Leggere tutte le risposte su questa pagina mi ha ispirato molto.

Quindi, ecco il mio suggerimento:

contenuto del file: 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



Esempio di utilizzo:
contenuto del file: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

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

exit 0


In esecuzione:

bash trap-test.sh

Produzione:

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!


Come puoi vedere dallo screenshot qui sotto, l'output è colorato e il messaggio di errore arriva nella lingua utilizzata.

enter image description here

71
Luca Borrione

Un'alternativa equivalente a "set -e" è

set -o errexit

Rende il significato della bandiera in qualche modo più chiaro di "-e".

Aggiunta casuale: per disattivare temporaneamente il flag e tornare al valore predefinito (di esecuzione continua indipendentemente dai codici di uscita), basta usare

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

Questo preclude la corretta gestione degli errori menzionata in altre risposte, ma è veloce ed efficace (proprio come bash).

22
Ben Scholbrock

Ispirato dalle idee presentate qui, ho sviluppato un modo leggibile e conveniente per gestire gli errori negli script di bash nel mio progetto bash boilerplate .

Semplicemente facendo il sourcing della libreria, si ottiene il seguente fuori dalla scatola (cioè si fermerà l'esecuzione su qualsiasi errore, come usando set -e grazie a un trap su ERR e alcuni bash-fu ):

bash-oo-framework error handling

Ci sono alcune funzionalità extra che aiutano a gestire gli errori, come try e catch , o il throw parola chiave, che consente di interrompere l'esecuzione in un punto per vedere il backtrace. Inoltre, se il terminale lo supporta, emette delle emoji powerline, colori parti dell'output per una buona leggibilità e sottolinea il metodo che ha causato l'eccezione nel contesto della riga di codice.

Il rovescio della medaglia è - non è portabile - il codice funziona in bash, probabilmente solo> = 4 (ma immagino che possa essere portato con un certo sforzo per bash 3).

Il codice è diviso in più file per una migliore gestione, ma sono stato ispirato dall'idea backtrace di la risposta di cui sopra di Luca Borrione .

Per saperne di più o dare un'occhiata alla fonte, vedi GitHub:

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

19
niieani

Preferisco qualcosa di veramente facile da chiamare. Quindi uso qualcosa che sembra un po 'complicato, ma è facile da usare. Di solito copio e incollo il codice qui sotto nei miei script. Una spiegazione segue il codice.

#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 ))`):"'

Di solito metto una chiamata alla funzione cleanup di fianco alla funzione error_exit, ma questo varia da script a script quindi l'ho lasciato fuori. Le trappole catturano i segnali di chiusura comuni e assicurano che tutto venga pulito. L'alias è ciò che fa la vera magia. Mi piace controllare tutto per il fallimento. Quindi in generale chiamo programmi in un "se!" dichiarazione di tipo. Sottraendo 1 dal numero di riga, l'alias mi dirà dove si è verificato l'errore. È anche facile da chiamare, e praticamente una prova idiota. Di seguito è riportato un esempio (basta sostituire/bin/false con quello che si sta per chiamare).

#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

Un'altra considerazione è il codice di uscita da restituire. Solo "1" è piuttosto standard, sebbene ci sia una manciata di codici di uscita riservati che bash usa sé stessi , e quella stessa pagina sostiene che i codici definiti dall'utente dovrebbero essere nell'intervallo 64-113 per conformarsi a Standard C/C++.

Si potrebbe anche considerare l'approccio del vettore di bit che mount utilizza per i suoi codici di uscita:

 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- i codici insieme consente allo script di segnalare più errori simultanei.

6
yukondude

Io uso il seguente codice trap, consente anche errori da tracciare attraverso pipe e comandi '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

Ho usato

die() {
        echo $1
        kill $$
}

prima; penso perché "l'uscita" stava fallendo per me per qualche motivo. Le impostazioni predefinite sopra sembrano comunque una buona idea.

3
pjz

Questo mi ha servito bene per un po 'ora. Stampa messaggi di errore o di avviso in rosso, una riga per parametro e consente un codice di uscita opzionale.

# 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

Non sono sicuro se questo ti sarà utile, ma ho modificato alcune delle funzioni suggerite qui per includere il controllo dell'errore (codice di uscita dal comando precedente) al suo interno. Ad ogni "verifica" passerò anche come parametro il "messaggio" di ciò che l'errore è a scopo di registrazione.

#!/bin/bash

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

Ora per chiamarlo nello stesso script (o in un altro se uso export -f error_exit) scrivo semplicemente il nome della funzione e passo un messaggio come parametro, come questo:

#!/bin/bash

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

rm *
error_exit "Unable to delete all files"

Usando questo sono stato in grado di creare un file bash veramente robusto per alcuni processi automatizzati e si fermerà in caso di errori e mi notificherà (log.sh lo farà)

2
Nelson Rodriguez

Questa funzione mi ha servito piuttosto bene di recente:

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

Si chiama aggiungendo 0 o l'ultimo valore restituito al nome del comando da eseguire, in modo da poter concatenare i comandi senza dover verificare i valori di errore. Con questo, questo blocco di istruzioni:

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

Diventa questo:

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

Se uno dei comandi fallisce, il codice di errore viene semplicemente passato alla fine del blocco. Trovo utile quando non si desidera eseguire i comandi successivi se uno precedente ha avuto esito negativo, ma non si desidera che lo script esca immediatamente (ad esempio, all'interno di un ciclo).

1
xarxziux

Questo trucco è utile per mancare comandi o funzioni. Il nome della funzione mancante (o eseguibile) verrà passato in $ _

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

Usare trap non è sempre un'opzione. Ad esempio, se stai scrivendo una sorta di funzione riutilizzabile che richiede la gestione degli errori e che può essere chiamata da qualsiasi script (dopo aver acquisito il file con le funzioni di supporto), quella funzione non può assumere nulla sul tempo di uscita dello script esterno, che rende molto difficile l'utilizzo delle trappole. Un altro svantaggio dell'utilizzo di trap è la cattiva componibilità, poiché si rischia di sovrascrivere il trap precedente che potrebbe essere impostato in precedenza nella catena del chiamante.

C'è un piccolo trucco che può essere usato per fare una corretta gestione degli errori senza trappole. Come forse già sapete da altre risposte, set -e non funziona nei comandi se si utilizza l'operatore || dopo di loro, anche se li si esegue in una subshell; ad esempio, questo non funzionerebbe:

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

Ma l'operatore || è necessario per impedire il ritorno dalla funzione esterna prima della pulizia. Il trucco consiste nell'eseguire il comando interno in background e quindi attendere immediatamente. Il wait builtin restituirà il codice di uscita del comando interno, e ora stai usando || dopo wait, non la funzione inner, quindi set -e funziona correttamente all'interno di quest'ultimo:

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

Ecco la funzione generica che si basa su questa idea. Dovrebbe funzionare in tutte le shell compatibili con POSIX se si rimuovono le parole chiave local, cioè si sostituisce tutto local x=y con solo 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" "[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
}

Esempio di utilizzo:

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

Esecuzione dell'esempio:

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

L'unica cosa di cui devi essere a conoscenza quando usi questo metodo è che tutte le modifiche delle variabili Shell fatte dal comando che passi a run non si propagheranno alla funzione chiamante, perché il comando viene eseguito in una subshell.

0
skozin