Script Shell de Saída Baseado no Código de Saída do Processo

378

Eu tenho um script de shell que executa uma série de comandos. Como faço para que o script do shell saia se algum dos comandos sair com um código de saída diferente de zero?

Mark Roddy
fonte
3
Eu respondi assumindo que você está usando o bash, mas se for um shell muito diferente, você pode especificar no seu post?
Martin W
Método rígido: teste o valor de $?depois de cada comando. Método fácil: coloque set -eou #!/bin/bash -ena parte superior do seu script Bash.
mwfearnley 15/01

Respostas:

494

Após cada comando, o código de saída pode ser encontrado na $?variável, para que você tenha algo como:

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

Você precisa ter cuidado com os comandos canalizados, pois o $?único fornece o código de retorno do último elemento no canal , portanto, no código:

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

não retornará um código de erro se o arquivo não existir (já que a sedparte do pipeline realmente funciona, retornando 0).

O bashshell realmente fornece uma matriz que pode ajudar nesse caso, nesse ser PIPESTATUS. Essa matriz possui um elemento para cada um dos componentes do pipeline, que você pode acessar individualmente, como ${PIPESTATUS[0]}:

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

Observe que isso está lhe dando o resultado do falsecomando, não o pipeline inteiro. Você também pode processar a lista inteira como achar melhor:

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

Se você deseja obter o maior código de erro de um pipeline, use algo como:

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

Isso passa por cada um dos PIPESTATUSelementos, por sua vez, armazenando-o rcse for maior que o rcvalor anterior .

paxdiablo
fonte
39
Mesmo recurso em apenas uma linha de código portátil: ls -al file.ext || exit $?([[]] não é portátil)
Março
19
MarcH, acho que você descobrirá que [[ ]]é bastante portátil bash, que é o que a pergunta está marcada :-) Estranhamente, lsnão funciona, command.comentão também não é portátil, especioso eu sei, mas é o mesmo tipo de argumento que você presente.
paxdiablo
39
Eu sei que isso é antigo, mas deve-se notar que você pode obter o código de saída dos comandos em um pipe através da matriz PIPESTATUS(ou seja, ${PIPESTATUS[0]}para o primeiro comando, ${PIPESTATUS[1]}para o segundo ou ${PIPESTATUS[*]}para uma lista de todos os status de saída.
DevSolar
11
É necessário enfatizar que scripts de shell elegantes e idiomáticos raramente precisam examinar $?diretamente. Você geralmente deseja algo como o if ls -al file.ext; then : nothing; else exit $?; fique, obviamente, como o @MarcH diz que é equivalente, ls -al file.ext || exit $?mas se as cláusulas thenou elseforem um pouco mais complexas, elas serão mais sustentáveis.
Tripleee
9
[[ $rc != 0 ]]lhe dará um 0: not foundou 1: not founderro. Isso deve ser alterado para [ $rc -ne 0 ]. Também rc=$?poderia ser removido e usado [ $? -ne 0 ].
CurtisLeeBolin
223

Se você quiser trabalhar com $ ?, precisará verificar após cada comando, já que $? é atualizado após a saída de cada comando. Isso significa que, se você executar um pipeline, obterá apenas o código de saída do último processo no pipeline.

Outra abordagem é fazer isso:

set -e
set -o pipefail

Se você colocar isso no topo do script de shell, parece que o bash cuidará disso para você. Como um pôster anterior observou, "set -e" fará com que o bash saia com um erro em qualquer comando simples. "set -o pipefail" fará com que o bash saia com um erro em qualquer comando de um pipeline também.

Veja aqui ou aqui para um pouco mais de discussão sobre esse problema. Aqui está a seção manual do bash no conjunto embutido.

Jeff Hill
fonte
6
Essa deve realmente ser a resposta principal: é muito, muito mais fácil fazer isso do que usar PIPESTATUSe verificar os códigos de saída em qualquer lugar.
CANDU
2
#!/bin/bash -eé a única maneira de iniciar um script de shell. Você sempre pode usar coisas como foo || handle_error $?se realmente precisar examinar os status de saída.
Davis Herring
53

" set -e" é provavelmente a maneira mais fácil de fazer isso. Basta colocar isso antes de qualquer comando no seu programa.

Allen
fonte
6
@SwaroopCH set -eseu script será cancelado se algum comando sair com o status de erro e você não o tratou.
Andrew
2
set -eé 100% equivalente ao set -o errexitqual, ao contrário do primeiro, pode ser pesquisado. Procure por documentação opengroup + errexit.
Março
30

Se você chamar exit no bash sem parâmetros, ele retornará o código de saída do último comando. Combinado com OR, o bash deve chamar apenas exit, se o comando anterior falhar. Mas eu não testei isso.

command1 || Saída;
command2 || Saída;

O Bash também armazenará o código de saída do último comando na variável $ ?.

Arvodan
fonte
25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
chemila
fonte
3
Isso não deveria ser exit $?(sem parênteses)?
precisa
21

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

  1. Como obtenho o código de saída cmd1emcmd1|cmd2

    Primeiro, observe que o cmd1código de saída pode ser diferente de zero e ainda não significa um erro. Isso acontece, por exemplo, em

    cmd | head -1

    você pode observar um status de saída 141 (ou 269 com ksh93) cmd1, mas é porque cmdfoi interrompido por um sinal SIGPIPE quando head -1finalizado após ter lido uma linha.

    Para conhecer o status de saída dos elementos de um pipeline cmd1 | cmd2 | cmd3

    uma. com zsh:

    Os códigos de saída são fornecidos na matriz especial pipestatus. cmd1o código de saída está dentro $pipestatus[1], o cmd3código de saída está dentro $pipestatus[3], para que $?seja sempre o mesmo que $pipestatus[-1].

    b. com bash:

    Os códigos de saída são fornecidos na PIPESTATUSmatriz especial. cmd1o código de saída está dentro ${PIPESTATUS[0]}, o cmd3código de saída está dentro ${PIPESTATUS[2]}, para que $?seja sempre o mesmo que ${PIPESTATUS: -1}.

    ...

    Para mais detalhes, consulte o seguinte link .

gatoatigrado
fonte
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;
}

fonte
19
Provavelmente não deve "sair 0", pois isso indica sucesso.
#
3
exit_code = $? echo "script abortado por causa de erros"; exit $ exit_code
RaSergiy
11
@ HAL9001 você tem evidências disso? A documentação da IBM diz o contrário .
Patrick James McDougle
11

No bash, isso é fácil, basta amarrá-los com &&:

command1 && command2 && command3

Você também pode usar a construção aninhada se:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
Martin W
fonte
+1 Esta foi a solução mais simples que eu estava procurando. Além disso, você também pode escrever if (! command)se espera um código de erro diferente de zero do comando.
Berci
isto é para comandos sequenciais .. e se eu quiser lançar esses 3 em paralelo e matar todos se algum deles falhar?
Vasile Surdu 26/03
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
fonte
2
Raramente há uma razão para usar $*; use "$@"para preservar espaços e caracteres curinga.
Davis Herring