it-swarm-es.tech

Manejo de errores en Bash

¿Cuál es tu método favorito para manejar los errores en Bash? El mejor ejemplo de manejo de errores que he encontrado en la web fue escrito por William Shotts, Jr en http://www.linuxcommand.org .

Él sugiere usar la siguiente función para el manejo de errores en 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."

¿Tiene una rutina de manejo de errores mejor que utiliza en los scripts de Bash?

210
Noob

Usa una trampa!

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

... entonces, cada vez que crees un archivo temporal:

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

y $temp_foo se eliminará al salir, y se imprimirá el número de línea actual. (set -e también le dará un comportamiento de salida en error, aunque viene con advertencias serias y debilita la previsibilidad y portabilidad del código).

Puede dejar que la captura llame a error por usted (en cuyo caso utiliza el código de salida predeterminado de 1 y ningún mensaje) o llámelo usted mismo y proporcione valores explícitos; por ejemplo:

error ${LINENO} "the foobar failed" 2

saldrá con el estado 2 y dará un mensaje explícito.

143
Charles Duffy

Esa es una buena solución. Solo quería añadir

set -e

como mecanismo de error rudimentario. Inmediatamente detendrá su script si falla un simple comando. Creo que este debería haber sido el comportamiento predeterminado: dado que tales errores casi siempre significan algo inesperado, no es realmente "sensato" seguir ejecutando los siguientes comandos.

112
Bruno De Fraine

Leer todas las respuestas en esta página me inspiró mucho.

Entonces, aquí está mi sugerencia:

contenido del archivo: 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



Ejemplo de uso:
contenido del archivo: trap-test.sh

#!/bin/bash

source 'lib.trap.sh'

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

exit 0


Corriendo:

bash trap-test.sh

Salida:

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!


Como puede ver en la captura de pantalla a continuación, la salida está coloreada y el mensaje de error aparece en el idioma utilizado.

enter image description here

71
Luca Borrione

Una alternativa equivalente a "set -e" es

set -o errexit

Hace que el significado de la bandera sea algo más claro que solo "-e".

Adición aleatoria: para deshabilitar temporalmente el indicador y volver al valor predeterminado (de ejecución continua independientemente de los códigos de salida), solo use

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

Esto excluye el manejo adecuado de errores mencionados en otras respuestas, pero es rápido y efectivo (como bash).

22
Ben Scholbrock

Inspirado por las ideas presentadas aquí, he desarrollado una forma legible y conveniente para manejar los errores en los scripts de bash en mi proyecto de placa de batalla de bash .

Al simplemente abastecerse de la biblioteca, obtiene lo siguiente de la caja (es decir, detendrá la ejecución de cualquier error, como si usara set -e gracias a una trap en ERR y algunos bash-fu):

bash-oo-framework error handling

Hay algunas características adicionales que ayudan a manejar los errores, como intentar y capturar , o la palabra clave throw , que le permite interrumpir la ejecución en un punto para ver el retroceso. Además, si el terminal lo admite, emite emojis de línea eléctrica, colorea partes de la salida para una mayor legibilidad y subraya el método que causó la excepción en el contexto de la línea de código.

El inconveniente es, no es portátil, el código funciona en bash, probablemente> = 4 solamente (pero me imagino que podría portarse con cierto esfuerzo para golpear 3).

El código se divide en varios archivos para un mejor manejo, pero me inspiró la idea de retroceso de la respuesta anterior de Luca Borrione .

Para leer más o echar un vistazo a la fuente, vea GitHub:

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

19
niieani

Prefiero algo realmente fácil de llamar. Así que uso algo que parece un poco complicado, pero es fácil de usar. Por lo general, solo copio y pego el código siguiente en mis scripts. Una explicación sigue el código.

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

Por lo general, pongo una llamada a la función de limpieza junto a la función error_exit, pero esto varía de un script a otro, así que lo dejé fuera. Las trampas atrapan las señales de terminación comunes y se aseguran de que todo se limpie. El alias es lo que hace la verdadera magia. Me gusta comprobar todo por el fracaso. Así que en general llamo a los programas en un "si!" tipo de declaración. Al restar 1 del número de línea, el alias me dirá dónde ocurrió la falla. También es muy simple de llamar, y bastante a prueba de idiotas. A continuación se muestra un ejemplo (simplemente reemplace/bin/false con lo que sea que llame).

#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

Otra consideración es el código de salida a devolver. Solo "1" es bastante estándar, aunque hay un puñado de códigos de salida reservados que bash usa , y esa misma página argumenta que los códigos definidos por el usuario deben estar en el rango 64-113 para cumplir con C/C++ normas.

También puede considerar el enfoque de vector de bits que utiliza mount para sus códigos de salida:

 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- al juntar los códigos le permite a su script señalar múltiples errores simultáneos.

6
yukondude

Utilizo el siguiente código de captura, también permite que los errores se rastreen a través de tuberías y comandos de "tiempo"

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

He usado

die() {
        echo $1
        kill $$
}

antes de; Creo que porque la "salida" me estaba fallando por alguna razón. Sin embargo, los valores predeterminados anteriores parecen ser una buena idea.

3
pjz

Esto me ha servido bien por un tiempo ahora. Imprime mensajes de error o advertencia en rojo, una línea por parámetro, y permite un código de salida opcional.

# 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

No estoy seguro de si esto será útil para usted, pero modifiqué algunas de las funciones sugeridas aquí para incluir la verificación del error (código de salida del comando anterior) dentro de él. En cada "verificación" también paso como parámetro el "mensaje" de cuál es el error para fines de registro.

#!/bin/bash

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

Ahora para llamarlo dentro del mismo script (o en otro si uso export -f error_exit) simplemente escribo el nombre de la función y paso un mensaje como parámetro, como este:

#!/bin/bash

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

rm *
error_exit "Unable to delete all files"

Usando esto pude crear un archivo bash realmente robusto para algún proceso automatizado y se detendrá en caso de errores y notificarme (log.sh lo hará)

2
Nelson Rodriguez

Esta función me ha estado sirviendo bastante bien recientemente:

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

Lo llama agregando 0 o el último valor de retorno al nombre del comando que se ejecutará, por lo que puede encadenar comandos sin tener que verificar los valores de error. Con esto, este bloque de declaración:

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

Se convierte en esto:

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 alguno de los comandos falla, el código de error simplemente se pasa al final del bloque. Me resulta útil cuando no desea que se ejecuten los comandos subsiguientes si falla uno anterior, pero tampoco desea que el script salga de inmediato (por ejemplo, dentro de un bucle).

1
xarxziux

Este truco es útil para faltar comandos o funciones. El nombre de la función que falta (o ejecutable) se pasará en $ _

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

Usar la trampa no siempre es una opción. Por ejemplo, si está escribiendo algún tipo de función reutilizable que necesita manejo de errores y que se puede llamar desde cualquier script (después de obtener el archivo con funciones de ayuda), esa función no puede asumir nada sobre el tiempo de salida del script externo, lo que hace que el uso de trampas sea muy difícil. Otra desventaja de usar trampas es la mala capacidad de composición, ya que corre el riesgo de sobrescribir las trampas anteriores que podrían configurarse anteriormente en la cadena de llamadas.

Hay un pequeño truco que se puede usar para hacer un manejo adecuado de errores sin trampas. Como ya sabrá por otras respuestas, set -e no funciona dentro de los comandos si usa el operador || después de ellos, incluso si los ejecuta en una subshell; Por ejemplo, esto no funcionaría:

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

Pero el operador || es necesario para evitar el regreso de la función externa antes de la limpieza. El truco consiste en ejecutar el comando interno en segundo plano y luego esperar inmediatamente. El wait builtin devolverá el código de salida del comando interno, y ahora está utilizando || después de wait, no la función interna, por lo que set -e funciona correctamente dentro de esta última:

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

Aquí está la función genérica que se basa en esta idea. Debería funcionar en todos los shells compatibles con POSIX si elimina las palabras clave local, es decir, reemplace todos los 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
}

Ejemplo de uso:

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

Ejecutando el ejemplo:

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

Lo único que debe tener en cuenta al usar este método es que todas las modificaciones de las variables de Shell realizadas desde el comando que pasa a run no se propagarán a la función de llamada, porque el comando se ejecuta en una subshell.

0
skozin