Salir de la secuencia de comandos basada en el código de salida del proceso

378

Tengo un script de shell que ejecuta una serie de comandos. ¿Cómo hago que salga el script de shell si alguno de los comandos sale con un código de salida distinto de cero?

Mark Roddy
fuente
3
Respondí asumiendo que estás usando bash, pero si es un shell muy diferente, ¿puedes especificarlo en tu publicación?
Martin W
Método difícil: prueba el valor de $?después de cada comando. Método fácil: poner set -eo #!/bin/bash -een la parte superior de su script Bash.
mwfearnley

Respuestas:

495

Después de cada comando, el código de salida se puede encontrar en la $?variable para que tenga algo como:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Debe tener cuidado con los comandos canalizados, ya que $?solo le da el código de retorno del último elemento en la tubería, por lo tanto, en el código:

ls -al file.ext | sed 's/^/xx: /"

no devolverá un código de error si el archivo no existe (ya que la sedparte de la tubería realmente funciona, devolviendo 0).

El bashshell realmente proporciona una matriz que puede ayudar en ese caso, ese ser PIPESTATUS. Esta matriz tiene un elemento para cada uno de los componentes de la tubería, al que puede acceder individualmente como ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Tenga en cuenta que esto le está dando el resultado del falsecomando, no toda la tubería. También puede obtener la lista completa para procesar como mejor le parezca:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

Si desea obtener el código de error más grande de una tubería, puede usar algo como:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Esto pasa a través de cada uno de los PIPESTATUSelementos a su vez, almacenándolos rcsi fue mayor que el rcvalor anterior .

paxdiablo
fuente
39
Misma función en solo una línea de código portátil: ls -al file.ext || exit $?([[]] no es portátil)
MarcH
19
MarcH, creo que encontrarás que [[ ]]es bastante portátil bash, que es en lo que está etiquetada la pregunta :-) Curiosamente, lsno funciona, command.comasí que tampoco es portátil, lo sé, pero es el mismo tipo de argumento que tú. presente.
paxdiablo
39
Sé que esto es antiguo, pero debe tenerse en cuenta que puede obtener el código de salida de los comandos en una tubería a través de la matriz PIPESTATUS(es decir, ${PIPESTATUS[0]}para el primer comando, ${PIPESTATUS[1]}para el segundo o ${PIPESTATUS[*]}para obtener una lista de todos los estados de salida.
DevSolar
11
Es necesario enfatizar que las secuencias de comandos de shell elegantes e idiomáticas rara vez necesitan examinarse $?directamente. Por lo general, desea algo como lo if ls -al file.ext; then : nothing; else exit $?; fique, por supuesto, como @MarcH dice que es equivalente, ls -al file.ext || exit $?pero si las cláusulas theno elseson algo más complejas, es más fácil de mantener.
tripleee el
99
[[ $rc != 0 ]]le dará un error 0: not foundo 1: not foundEsto debería cambiarse a [ $rc -ne 0 ]. También rc=$?podría ser eliminado y simplemente utilizado [ $? -ne 0 ].
CurtisLeeBolin
223

Si desea trabajar con $ ?, deberá verificarlo después de cada comando, ya que $? se actualiza después de cada comando sale. Esto significa que si ejecuta una tubería, solo obtendrá el código de salida del último proceso en la tubería.

Otro enfoque es hacer esto:

set -e
set -o pipefail

Si coloca esto en la parte superior del script de shell, parece que bash se encargará de esto por usted. Como señaló un póster anterior, "set -e" hará que bash salga con un error en cualquier comando simple. "set -o pipefail" hará que bash salga con un error en cualquier comando en una tubería también.

Vea aquí o aquí para un poco más de discusión sobre este problema. Aquí está la sección del manual de bash en el conjunto incorporado.

Jeff Hill
fuente
66
Esta realmente debería ser la respuesta principal: es mucho, mucho más fácil hacer esto que usar PIPESTATUSy verificar los códigos de salida en todas partes.
candu
2
#!/bin/bash -ees la única forma de iniciar un script de shell. Siempre puede usar cosas como foo || handle_error $?si realmente necesita examinar los estados de salida.
Davis Herring
53

" set -e" es probablemente la forma más fácil de hacer esto. Simplemente ponga eso antes de cualquier comando en su programa.

Allen
fuente
66
@SwaroopCH set -esu secuencia de comandos se cancelará si algún comando en su secuencia de comandos sale con estado de error y usted no manejó este error.
Andrew
2
set -ees 100% equivalente al set -o errexitque a diferencia del primero se puede buscar. Busque opengroup + errexit para obtener documentación oficial.
MarcH
30

Si solo llama a exit en el bash sin parámetros, devolverá el código de salida del último comando. Combinado con OR, el bash solo debería invocar la salida, si falla el comando anterior. Pero no he probado esto.

comando1 || salida;
comando2 || salida;

Bash también almacenará el código de salida del último comando en la variable $ ?.

Arvodan
fuente
25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
chemila
fuente
3
¿No debería ser esto exit $?(sin padres)?
Malté
21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. ¿Cómo obtengo el código de salida de cmd1encmd1|cmd2

    Primero, tenga en cuenta que el cmd1código de salida podría ser distinto de cero y aún así no significa un error. Esto sucede por ejemplo en

    cmd | head -1

    puede observar un estado de salida 141 (o 269 con ksh93) cmd1, pero es porque cmdfue interrumpido por una señal SIGPIPE cuando head -1terminó después de haber leído una línea.

    Conocer el estado de salida de los elementos de una tubería. cmd1 | cmd2 | cmd3

    a. con zsh:

    Los códigos de salida se proporcionan en la matriz especial pipestatus. cmd1el código de salida está en $pipestatus[1], el cmd3código de salida en $pipestatus[3], de modo que $?siempre es el mismo que $pipestatus[-1].

    si. con golpe:

    Los códigos de salida se proporcionan en la PIPESTATUSmatriz especial. cmd1el código de salida está en ${PIPESTATUS[0]}, el cmd3código de salida en ${PIPESTATUS[2]}, de modo que $?siempre es el mismo que ${PIPESTATUS: -1}.

    ...

    Para más detalles ver el siguiente enlace .

gatoatigrado
fuente
19

para bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

fuente
19
Probablemente no debería "salir de 0", ya que eso indica éxito.
nobar
3
exit_code = $ ?; echo "script cancelado debido a errores"; exit $ exit_code
RaSergiy
1
@ HAL9001, ¿tiene evidencia de esto? La documentación de IBM dice lo contrario .
Patrick James McDougle
11

En bash, esto es fácil, solo átalos con &&:

command1 && command2 && command3

También puede usar la construcción anidada if:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
Martin W
fuente
+1 Esta fue la solución más simple que estaba buscando. Además, también puede escribir if (! command)si espera un código de error distinto de cero del comando.
Berci
esto es para comandos secuenciales ... ¿y si quiero lanzar esos 3 en paralelo y matar a todos si alguno falla?
Vasile Surdu
4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit
Yordan Georgiev
fuente
2
Rara vez hay una razón para usar $*; use "$@"en su lugar para preservar espacios y comodines.
Davis Herring