Interrompendo um script de shell se algum comando retornar um valor diferente de zero?

437

Eu tenho um script de shell Bash que chama uma série de comandos. Eu gostaria que o script shell saísse automaticamente com um valor de retorno 1 se algum dos comandos retornar um valor diferente de zero.

Isso é possível sem verificar explicitamente o resultado de cada comando?

por exemplo

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
fonte
8
Além de set -e, também faça set -u(ou set -eu). -upõe fim ao comportamento idiota e oculto que você pode acessar qualquer variável inexistente e ter um valor em branco produzido sem diagnóstico.
Kaz

Respostas:

742

Adicione isso ao início do script:

set -e

Isso fará com que o shell saia imediatamente se um comando simples sair com um valor de saída diferente de zero. Um comando simples é qualquer comando que não faça parte de um if, while, ou até test, ou parte de um && ou || Lista.

Veja a página de manual bash (1) no comando interno "set" para mais detalhes.

Pessoalmente, inicio quase todos os scripts de shell com "set -e". É realmente irritante ter um script continuar teimosamente quando algo falha no meio e quebra suposições para o resto do script.

Ville Laurikari
fonte
36
Isso funcionaria, mas eu gosto de usar "#! / Usr / bin / env bash" porque eu frequentemente corro o bash de outro lugar que não seja / bin. E "#! / Usr / bin / env bash -e" não funciona. Além disso, é bom ter um lugar para modificar para ler "set -xe" quando eu quiser ativar o rastreamento para depuração.
Ville Laurikari
48
Além disso, os sinalizadores na linha shebang são ignorados se um script for executado como bash script.sh.
Tom Anderson
27
Apenas uma observação: se você declarar funções dentro do script bash, as funções precisarão ter -e redeclared dentro do corpo da função, se você desejar estender essa funcionalidade.
Jin Kim
8
Além disso, se você criar seu script, a linha shebang será irrelevante.
4
@JinKim Esse não parece ser o caso do bash 3.2.48. Tente o seguinte dentro de um script: set -e; tf() { false; }; tf; echo 'still here'. Mesmo sem set -edentro do corpo tf(), a execução é abortada. Talvez você quisesse dizer que set -enão é herdado por sub-conchas , o que é verdade.
usar o seguinte código
202

Para adicionar à resposta aceita:

Lembre-se de que set -eàs vezes não basta, especialmente se você tiver cachimbos.

Por exemplo, suponha que você tenha esse script

#!/bin/bash
set -e 
./configure  > configure.log
make

... que funciona como esperado: um erro configureinterrompe a execução.

Amanhã você faz uma mudança aparentemente trivial:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... e agora não funciona. Isso é explicado aqui e é fornecida uma solução alternativa (somente Bash):

#! / bin / bash
set -e 
set -o pipefail

./configurar | tee configure.log
faço
leonbloy
fonte
1
Obrigado por explicar a importância de ter pipefailque concordar set -o!
Malcolm
83

As instruções if no seu exemplo são desnecessárias. Apenas faça assim:

dosomething1 || exit 1

Se você seguir o conselho de Ville Laurikari e usá-lo set -e, em alguns comandos, poderá ser necessário:

dosomething || true

O || truefará com que o pipeline de comando tem um truevalor de retorno, mesmo se o comando falhar de modo que o da -eopção não irá matar o script.

Zan Lynx
fonte
1
Eu gosto disso. Especialmente porque a resposta principal é centrada no bash (não está claro para mim se / em que medida se aplica ao script zsh). E eu poderia procurar, mas você é mais claro, porque é lógico.
G33kz0r
set -enão é centrado no bash - é suportado mesmo no Bourne Shell original.
Marcos Vives Del Sol
27

Se você precisar fazer uma limpeza na saída, também poderá usar 'trap' com o pseudo-sinal ERR. Isso funciona da mesma maneira que interceptar INT ou qualquer outro sinal; bash lança ERR se algum comando sair com um valor diferente de zero:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Ou, especialmente se você estiver usando "set -e", poderá capturar EXIT; sua armadilha será executada quando o script sair por qualquer motivo, incluindo um final normal, interrupções, uma saída causada pela opção -e etc.

fholo
fonte
12

A $?variável raramente é necessária. O pseudo-idioma command; if [ $? -eq 0 ]; then X; fideve sempre ser escrito como if command; then X; fi.

Os casos em que $?é necessário é quando ele precisa ser verificado em relação a vários valores:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

ou quando $?precisar ser reutilizado ou manipulado:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
fonte
3
Por que "sempre deve ser escrito como"? Quero dizer, por que " deveria " ser assim? Quando um comando é longo (pense em chamar o GCC com uma dúzia de opções), é muito mais legível executar o comando antes de verificar o status de retorno.
ysap
Se um comando for muito longo, você pode dividi-lo nomeando-o (defina uma função shell).
Mark Edgar
12

Execute-o com -eou set -eno topo.

Veja também set -u.

grumoso
fonte
34
Para potencialmente salvar outras pessoas, a necessidade de ler help set: -utrata as referências a variáveis ​​não definidas como erros.
usar o seguinte código
1
então é set -uou set -enão, ou ambos? @lumpynose
ericn
1
@eric me aposentei há vários anos. Mesmo que eu amei meu trabalho, meu cérebro envelhecido esqueceu tudo. De improviso, acho que você poderia usar os dois juntos; palavras ruins da minha parte; Eu deveria ter dito "e / ou".
Lumpynose
3

Uma expressão como

dosomething1 && dosomething2 && dosomething3

interromperá o processamento quando um dos comandos retornar com um valor diferente de zero. Por exemplo, o seguinte comando nunca imprimirá "concluído":

cat nosuchfile && echo "done"
echo $?
1
gabor
fonte
2
#!/bin/bash -e

deve ser suficiente.

Baligh Uddin
fonte
-2

apenas lançando outro para referência, pois houve uma pergunta adicional na entrada de Mark Edgars e aqui está um exemplo adicional e aborda o tópico geral:

[[ `cmd` ]] && echo success_else_silence

que é o mesmo cmd || exit errcodeque alguém mostrou.

por exemplo. Quero garantir que uma partição seja desmontada se montada:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
fonte
5
Não, [[ cmd`]] `não é a mesma coisa. É falso se a saída do comando estiver vazia e verdadeira, caso contrário, independentemente do status de saída do comando.
Gilles 'SO- stop be evil'