Em um script Bash, como posso sair do script inteiro se ocorrer uma determinada condição?

717

Estou escrevendo um script no Bash para testar algum código. No entanto, parece tolo executar os testes se a compilação do código falhar em primeiro lugar; nesse caso, eu simplesmente abortarei os testes.

Existe uma maneira de fazer isso sem envolver o script inteiro dentro de um loop while e usar quebras? Algo como um dun dun dun goto?

samoz
fonte

Respostas:

810

Tente esta afirmação:

exit 1

Substitua 1pelos códigos de erro apropriados. Consulte também Códigos de saída com significados especiais .

Michael Foukarakis
fonte
4
@CMCDragonkai, geralmente qualquer código diferente de zero funcionará. Se você não precisa de nada de especial, basta usar de forma 1consistente. Se o script for executado por outro script, convém definir seu próprio conjunto de códigos de status com um significado específico. Por exemplo, 1== testes falharam, 2== falha na compilação. Se o script fizer parte de outra coisa, pode ser necessário ajustar os códigos para corresponder às práticas usadas lá. Por exemplo, quando parte do conjunto de testes é executada pela automake, o código 77é usado para marcar um teste como ignorado.
Michał Górny
21
sem esta fecha a janela também, não apenas sair do script
Toni Leigh
7
O @ToniLeigh bash não tem o conceito de "janela", você provavelmente está confuso com relação ao que sua configuração específica - por exemplo, um emulador de terminal - faz.
Michael Foukarakis 21/03
4
@ToniLeigh Se estiver fechando a "janela", provavelmente você está colocando o exit #comando dentro de uma função, não de um script. (Nesse caso, use-o return #.)
Jamie
1
@Sevenearths 0 significa que foi bem sucedido, assim, exit 0sair do script e retorna 0 (contando outros scripts que possam utilizar o resultado deste roteiro foi bem sucedido)
VictorGalisson
689

Use set -e

#!/bin/bash

set -e

/bin/command-that-fails
/bin/command-that-fails2

O script será encerrado após a primeira linha que falhar (retorna código de saída diferente de zero). Nesse caso, o comando that-fail2 não será executado.

Se você verificar o status de retorno de cada comando, seu script terá a seguinte aparência:

#!/bin/bash

# I'm assuming you're using make

cd /project-dir
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

cd /project-dir2
make
if [[ $? -ne 0 ]] ; then
    exit 1
fi

Com set -e , seria semelhante a:

#!/bin/bash

set -e

cd /project-dir
make

cd /project-dir2
make

Qualquer comando que falhar fará com que o script inteiro falhe e retorne um status de saída que você pode verificar com $? . Se o seu script for muito longo ou você estiver criando muitas coisas, ficará muito feio se você adicionar verificações de status de retorno em todos os lugares.

Shizzmo
fonte
10
Com set -eVocê ainda pode fazer alguns comandos saída com erros sem interromper o script: command 2>&1 || echo $?.
Adobe
6
set -eabortará o script se um pipeline ou estrutura de comando retornar um valor diferente de zero. Por exemplo foo || bar, falhará apenas se ambos fooe barretornarem um valor diferente de zero. Geralmente, um script bash bem escrito funcionará se você adicionar set -eno início e a adição funcionar como uma verificação de integridade automática: interrompa o script se algo der errado.
Mikko Rantalainen
6
Se você estiver canalizando comandos juntos, também poderá falhar se algum deles falhar definindo a set -o pipefailopção
Jake Biesinger
18
Na verdade, o código idiomático sem set -eseria justo make || exit $?.
Tripleee 18/09/2013
4
Também você tem set -u. Dê uma olhada ao unofficial modo estrito festa : set -euo pipefail.
Pablo A
236

Um cara do SysOps me ensinou uma vez a técnica da garra com três dedos:

yell() { echo "$0: $*" >&2; }
die() { yell "$*"; exit 111; }
try() { "$@" || die "cannot $*"; }

Essas funções são * NIX OS e shell-robustas. Coloque-os no início do seu script (bash ou não),try() sua declaração e código.

Explicação

(com base no comentário de ovelhas voadoras ).

  • yell: imprima o nome do script e todos os argumentos para stderr:
    • $0 é o caminho para o script;
    • $* são todos argumentos.
    • >&2significa >redirecionar stdout para o & pipe2 . tubo1 seria em stdoutsi.
  • diefaz o mesmo que yell, mas sai com um status de saída diferente de 0 , o que significa "falha".
  • tryusa o ||(booleano OR), que avalia apenas o lado direito se o esquerdo falhar.
    • $@é todos os argumentos novamente, mas diferentes .
c.gutierrez
fonte
3
Eu sou bastante novo no script unix. Você pode explicar como as funções acima são executadas? Estou vendo uma nova sintaxe com a qual não estou familiarizado. Obrigado.
kaizenCoder
15
gritar : $0é o caminho para o script. $*são todos argumentos. >&2significa " >redirecionar stdout para &canal 2". o tubo 1 seria o próprio padrão. então grite prefixo todos os argumentos com o nome do script e imprima para stderr. die faz o mesmo que gritar , mas sai com um status de saída diferente de 0, o que significa "falha". try usa o booleano ou ||, que avalia apenas o lado direito se o esquerdo não falhar. $@é todos os argumentos novamente, mas diferentes . esperança de que explica tudo
voando ovelhas
1
Eu modifiquei isso para die() { yell "$1"; exit $2; }que você possa passar uma mensagem e sair com o código die "divide by zero" 115.
Mark Lakata
3
Eu posso ver como eu usaria yelle die. No entanto, trynem tanto. Você pode fornecer um exemplo de como usá-lo?
kshenoy
2
Hmm, mas como você os usa em um script? Não entendo o que você quer dizer com "try () sua declaração e código".
TheJavaGuy-Ivan Milosavljević 18/03
33

Se você chamar o script source, poderá usar return <x>where<x> estará o status de saída do script (use um valor diferente de zero para erro ou falso). Mas se você chamar um script executável (ou seja, diretamente com seu nome de arquivo), a declaração de retorno resultará em uma reclamação (mensagem de erro "return: pode apenas 'retornar' de uma função ou script de origem").

Se exit <x>for usado, quando o script for chamado source, resultará na saída do shell que iniciou o script, mas um script executável será encerrado, conforme o esperado.

Para lidar com ambos os casos no mesmo script, você pode usar

return <x> 2> /dev/null || exit <x>

Isso manipulará qualquer chamada que seja adequada. Isso pressupõe que você usará essa declaração no nível superior do script. Eu desaconselharia a saída direta do script de dentro de uma função.

Nota: <x>é suposto ser apenas um número.

kavadias
fonte
Não funciona para mim em um script com retorno / saída dentro de uma função, ou seja, é possível sair dentro da função sem existir o shell, mas sem fazer com que o responsável pela função cuide da verificação correta do código de retorno da função ?
janeiro
@jan O que o chamador faz (ou não faz) com os valores de retorno é completamente ortogonal (ou seja, independente de) como você retorna da função (... sem sair do shell, independentemente da chamada). É principalmente depende do código chamador, que não faz parte desta Q & A. Você poderia até mesmo adaptar o valor de retorno da função para as necessidades do chamador, mas esta resposta não limitar o que este valor de retorno pode ser ...
Kavadias
11

Costumo incluir uma função chamada run () para lidar com erros. Cada chamada que eu quero fazer é passada para essa função, para que o script inteiro saia quando uma falha for atingida. A vantagem disso sobre a solução set -e é que o script não sai silenciosamente quando uma linha falha e pode lhe dizer qual é o problema. No exemplo a seguir, a terceira linha não é executada porque o script sai na chamada para false.

function run() {
  cmd_output=$(eval $1)
  return_value=$?
  if [ $return_value != 0 ]; then
    echo "Command $1 failed"
    exit -1
  else
    echo "output: $cmd_output"
    echo "Command succeeded."
  fi
  return $return_value
}
run "date"
run "false"
run "date"
Joseph Sheedy
fonte
1
Cara, por algum motivo, eu realmente gosto desta resposta. Reconheço que é um pouco mais complicado, mas parece tão útil. E, como eu não sou especialista em bash, isso me leva a acreditar que minha lógica está com defeito, e há algo errado com essa metodologia; caso contrário, acho que outros teriam elogiado mais. Então, qual é o problema dessa função? Há algo que eu deveria estar procurando aqui?
Não me lembro de minha razão para usar eval, a função funciona bem com cmd_output = $ (US $ 1)
Joseph Sheedy
Acabei de implementar isso como parte de um processo de implantação complexo e funcionou de maneira fantástica. Obrigado e aqui está um comentário e um voto positivo.
Fuzzygroup
Trabalho verdadeiramente incrível! Esta é a solução mais simples e limpa que funciona bem. Para mim, eu adicionei isso antes de um comando em um loop FOR, pois os loops FOR não capturam o set -e option. Em seguida, o comando, uma vez que é com argumentos, eu usei aspas simples para evitar problemas festança assim: runTry 'mysqldump $DB_PASS --user="$DB_USER" --host="$BV_DB_HOST" --triggers --routines --events --single-transaction --verbose $DB_SCHEMA $tables -r $BACKUP_DIR/$tables$BACKUP_FILE_NAME'. Nota: alterei o nome da função para runTry.
Tony-Caffe
1
evalé potencialmente perigoso se você aceitar entradas arbitrárias, mas, caso contrário, isso parecerá muito bom.
precisa saber é o seguinte
5

Em vez de ifconstruir, você pode aproveitar a avaliação de curto-circuito :

#!/usr/bin/env bash

echo $[1+1]
echo $[2/0]              # division by 0 but execution of script proceeds
echo $[3+1]
(echo $[4/0]) || exit $? # script halted with code 1 returned from `echo`
echo $[5+1]

Observe o par de parênteses necessário devido à prioridade do operador de alternância. $?é uma variável especial configurada para sair do código do comando chamado mais recentemente.

skalee
fonte
1
se eu fizer command -that --fails || exit $?isso sem parênteses, o echo $[4/0]que nos leva a precisar deles?
Anentropic
2
@Anentropic @skalee Os parênteses não têm nada a ver com precedência, mas com o tratamento de exceções. Uma divisão por zero causará uma saída imediata do shell com o código 1. Sem os parênteses (isto é, um simples echo $[4/0] || exit $?) bash nunca executará o echo, e muito menos obedecerá ao ||.
bobbogo 4/03/19
1

Eu tenho a mesma pergunta, mas não posso perguntar, porque seria uma duplicata.

A resposta aceita, usando exit, não funciona quando o script é um pouco mais complicado. Se você usar um processo em segundo plano para verificar a condição, a saída sai apenas desse processo, pois ele é executado em um subcasca. Para matar o script, você deve explicitamente matá-lo (pelo menos é o único jeito que eu sei).

Aqui está um pequeno script sobre como fazê-lo:

#!/bin/bash

boom() {
    while true; do sleep 1.2; echo boom; done
}

f() {
    echo Hello
    N=0
    while
        ((N++ <10))
    do
        sleep 1
        echo $N
        #        ((N > 5)) && exit 4 # does not work
        ((N > 5)) && { kill -9 $$; exit 5; } # works 
    done
}

boom &
f &

while true; do sleep 0.5; echo beep; done

Esta é uma resposta melhor, mas ainda incompleta. Realmente não sei como me livrar da parte do boom .

Gyro Gearloose
fonte