Como sair se um comando falhou?

222

Eu sou um noob em scripts de shell. Quero imprimir uma mensagem e sair do meu script se um comando falhar. Eu tentei:

my_command && (echo 'my_command failed; exit)

mas não funciona. Ele continua executando as instruções após esta linha no script. Estou usando o Ubuntu e o bash.

user459246
fonte
5
Você pretendia que a cotação não fechada fosse um erro de sintaxe que causaria falha / saída? caso contrário, feche a cotação no seu exemplo.
hobs

Respostas:

406

Experimentar:

my_command || { echo 'my_command failed' ; exit 1; }

Quatro mudanças:

  • Mude &&para||
  • Use { }no lugar de( )
  • Introduzir ;depois exite
  • espaços {antes e antes}

Como você deseja imprimir a mensagem e sair somente quando o comando falhar (sai com um valor diferente de zero), você ||não precisa de um &&.

cmd1 && cmd2

será executado cmd2quando cmd1for bem-sucedido (valor de saída 0). Enquanto que

cmd1 || cmd2

será executado cmd2quando cmd1falhar (valor de saída diferente de zero).

Usar ( )faz com que o comando dentro deles seja executado em um sub shell e chamar um exitde lá faz com que você saia do sub shell e não do shell original; portanto, a execução continua no shell original.

Para superar esse uso { }

As duas últimas alterações são exigidas pelo bash.

codaddict
fonte
4
Parece estar "invertido". Se uma função "for bem-sucedida", ela retornará 0 e, se "falhar", retornará diferente de zero, portanto &&& deverá avaliar quando a primeira metade retornou diferente de zero. Isso não significa que a resposta acima esteja incorreta - não, está correta. && e || nos scripts funcionam com base no sucesso e não no valor de retorno.
Cashcow
7
Parece invertido, mas leia-o e faz sentido: "execute este comando (com êxito)" OU "imprima este erro e saia" #
simpleuser
2
A lógica por trás disso é que a linguagem usa avaliação de curto-circuito (SCE). Com o SCE, f a expressão tem a forma "p OR q", e p é avaliado como verdadeiro, então não há razão para olhar para q. Se a expressão tiver a forma "p AND q", e p for avaliada como falsa, não há razão para analisar q. A razão para isso é dupla: 1) é mais rápida, por razões óbvias, e 2) evita certos tipos de erros (por exemplo: "se x! = 0 AND 10 / x> 5" falhará se não houver SCE ) A capacidade de usá-lo na linha de comando como este é um efeito colateral feliz.
user2635263
2
Eu recomendaria ecoando a STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop
127

As outras respostas abordaram bem a pergunta direta, mas você também pode estar interessado em usá-lo set -e. Com isso, qualquer comando que falhar (fora de contextos específicos, como iftestes) fará com que o script seja interrompido. Para certos scripts, é muito útil.

Daenyth
fonte
3
Infelizmente, também é muito perigoso - set -epossui um longo e misterioso conjunto de regras sobre quando funciona e quando não funciona. (Se alguém executar if yourfunction; then ..., set -enunca será acionado por dentro yourfunctionou por qualquer coisa que ele chamar, porque esse código é considerado "verificado"). Consulte a seção de exercícios do BashFAQ # 105 , discutindo esta e outras armadilhas (algumas das quais se aplicam apenas a versões específicas do shell, portanto, você deve testar todos os lançamentos que podem ser usados ​​para executar seu script).
Charles Duffy #
67

Observe também que o status de saída de cada comando é armazenado na variável do shell $ ?, que você pode verificar imediatamente após a execução do comando. Um status diferente de zero indica falha:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
fonte
10
Isso pode ser substituído apenas se my_command, não for necessário usar o comando test aqui.
Bart Sas
5
+1 porque acho que você não deveria ser punido por listar essa alternativa - ela deveria estar na mesa - embora seja meio feio e, na minha experiência, seja muito fácil executar outro comando no meio sem perceber a natureza do teste (talvez eu ' sou apenas estúpido).
quer
1
@ BartSas Se o comando for longo, é melhor colocá-lo em sua própria linha. Isso torna o script mais legível.
Michael
@ Michael, não se ele criar dependências entre linhas, onde você precisa saber o que aconteceu na linha A antes de entender a linha B (ou, pior, precisa saber o que acontecerá na linha B antes de saber que é seguro adicionar algo novo após o final da linha A). Eu já vi muitas vezes que alguém adicionou um echo "Just finished step A", sem saber que isso echosubstituiu o $?que seria verificado mais tarde.
Charles Duffy
67

Se você deseja esse comportamento para todos os comandos do seu script, basta adicionar

  set -e 
  set -o pipefail

no início do script. Esse par de opções informa ao interpretador bash para sair sempre que um comando retornar com um código de saída diferente de zero.

Isso não permite que você imprima uma mensagem de saída.

damienfrancois
fonte
8
Você pode executar comandos na saída usando o trapcomando interno do bash.
Gavin Smith
Esta é a resposta para a pergunta XY.
Jwg 6/11
5
Pode ser interessante explicar o que os comandos fazem independentemente, e não o par. Especialmente porque set -o pipefailpode não ser o comportamento desejado. Ainda obrigado por apontar!
Antoine Pinsard
3
set -enão é de todo confiável, consistente ou previsível! Para convencer-se disso, reveja a seção exercícios de BashFAQ # 105 , ou a tabela comparando os comportamentos dos diferentes conchas na in-ulm.de/~mascheck/various/set-e
Charles Duffy
14

Eu hackeei o seguinte idioma:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Preceda cada comando com um eco informativo e siga cada comando com a mesma
if [ $? -ne 0 ];...linha. (Obviamente, você pode editar essa mensagem de erro, se desejar.)

Grant Birchmeier
fonte
Isso pode ser definido como uma função, reutilizando-o.
Eric Wang
1
'se [$? -ne 0]; então ... fi 'é uma maneira complicada de escrever' || '
Kevin Cline
if ! javac *.java; then ...- por que se preocupar $??
Charles Duffy
Charles - porque eu sou um roteirista de bash medíocre na melhor das hipóteses :) #
Grant Birchmeier
10

Fornecido my_commandé canonicamente projetado, ou seja , retorna 0 quando for bem-sucedido, então &&é exatamente o oposto do que você deseja. Você quer ||.

Observe também que (não me parece correto no bash, mas não posso tentar de onde estou. Conte-me.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
fonte
IIRC, você precisa de ponto-e-vírgula após cada uma das linhas dentro de {} #
Alex Howansky
9
@ Alex: não se eles estiverem em linha separada.
Pausado até novo aviso.
5

Você também pode usar, se quiser preservar o status do erro de saída, e ter um arquivo legível com um comando por linha:

my_command1 || exit $?
my_command2 || exit $?

Isso, no entanto, não imprimirá nenhuma mensagem de erro adicional. Mas, em alguns casos, o erro será impresso pelo comando com falha de qualquer maneira.

alexpirina
fonte
1
Não há razão para que seja exit $?; apenas exitusa $?como seu valor padrão.
Charles Duffy
@CharlesDuffy não sabia. Incrível, por isso é ainda mais simples!
alexpirine
3

O trapshell embutido permite capturar sinais e outras condições úteis, incluindo falha na execução de comandos (ou seja, um status de retorno diferente de zero). Portanto, se você não quiser testar explicitamente o status de retorno de cada comando único, poderá dizer trap "your shell code" ERRe o código do shell será executado sempre que um comando retornar um status diferente de zero. Por exemplo:

trap "echo script failed; exit 1" ERR

Observe que, como em outros casos de captura de comandos com falha, os pipelines precisam de tratamento especial; o acima não vai pegar false | true.

kozel
fonte