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?
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:
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.
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.
@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 $ ?.
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}.
# 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;}
+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
$?
depois de cada comando. Método fácil: coloqueset -e
ou#!/bin/bash -e
na parte superior do seu script Bash.Respostas:
Após cada comando, o código de saída pode ser encontrado na
$?
variável, para que você tenha algo como: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:não retornará um código de erro se o arquivo não existir (já que a
sed
parte do pipeline realmente funciona, retornando 0).O
bash
shell realmente fornece uma matriz que pode ajudar nesse caso, nesse serPIPESTATUS
. Essa matriz possui um elemento para cada um dos componentes do pipeline, que você pode acessar individualmente, como${PIPESTATUS[0]}
:Observe que isso está lhe dando o resultado do
false
comando, não o pipeline inteiro. Você também pode processar a lista inteira como achar melhor:Se você deseja obter o maior código de erro de um pipeline, use algo como:
Isso passa por cada um dos
PIPESTATUS
elementos, por sua vez, armazenando-orc
se for maior que orc
valor anterior .fonte
ls -al file.ext || exit $?
([[]] não é portátil)[[ ]]
é bastante portátilbash
, que é o que a pergunta está marcada :-) Estranhamente,ls
não funciona,command.com
então também não é portátil, especioso eu sei, mas é o mesmo tipo de argumento que você presente.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.$?
diretamente. Você geralmente deseja algo como oif ls -al file.ext; then : nothing; else exit $?; fi
que, obviamente, como o @MarcH diz que é equivalente,ls -al file.ext || exit $?
mas se as cláusulasthen
ouelse
forem um pouco mais complexas, elas serão mais sustentáveis.[[ $rc != 0 ]]
lhe dará um0: not found
ou1: not found
erro. Isso deve ser alterado para[ $rc -ne 0 ]
. Tambémrc=$?
poderia ser removido e usado[ $? -ne 0 ]
.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:
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.
fonte
PIPESTATUS
e verificar os códigos de saída em qualquer lugar.#!/bin/bash -e
é a única maneira de iniciar um script de shell. Você sempre pode usar coisas comofoo || handle_error $?
se realmente precisar examinar os status de saída."
set -e
" é provavelmente a maneira mais fácil de fazer isso. Basta colocar isso antes de qualquer comando no seu programa.fonte
set -e
seu script será cancelado se algum comando sair com o status de erro e você não o tratou.set -e
é 100% equivalente aoset -o errexit
qual, ao contrário do primeiro, pode ser pesquisado. Procure por documentação opengroup + errexit.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.
O Bash também armazenará o código de saída do último comando na variável $ ?.
fonte
fonte
exit $?
(sem parênteses)?http://cfaj.freeshell.org/shell/cus-faq-2.html#11
Como obtenho o código de saída
cmd1
emcmd1|cmd2
Primeiro, observe que o
cmd1
código de saída pode ser diferente de zero e ainda não significa um erro. Isso acontece, por exemplo, emvocê pode observar um status de saída 141 (ou 269 com ksh93)
cmd1
, mas é porquecmd
foi interrompido por um sinal SIGPIPE quandohead -1
finalizado 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.
cmd1
o código de saída está dentro$pipestatus[1]
, ocmd3
có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
PIPESTATUS
matriz especial.cmd1
o código de saída está dentro${PIPESTATUS[0]}
, ocmd3
código de saída está dentro${PIPESTATUS[2]}
, para que$?
seja sempre o mesmo que${PIPESTATUS: -1}
....
Para mais detalhes, consulte o seguinte link .
fonte
para bash:
fonte
No bash, isso é fácil, basta amarrá-los com &&:
Você também pode usar a construção aninhada se:
fonte
if (! command)
se espera um código de erro diferente de zero do comando.fonte
$*
; use"$@"
para preservar espaços e caracteres curinga.