Como sair de um script de shell se uma parte dele falhar?

51

Como posso escrever um script de shell que saia, se uma parte falhar? Por exemplo, se o seguinte trecho de código falhar, o script deverá sair.

n=0
until [ $n -ge 5 ]
do
  gksu *command* && break
  n=$[$n+1]
  sleep 3
Weylyn
fonte

Respostas:

88

Uma abordagem seria adicionar set -eao início do seu script. Isso significa (de help set):

  -e  Exit immediately if a command exits with a non-zero status.

Portanto, se algum de seus comandos falhar, o script será encerrado.

Como alternativa, você pode adicionar exitinstruções explícitas nos possíveis pontos de falha:

command || exit 1
terdon
fonte
5
Eu (e o bash wiki ) preferimos que as pessoas pensem no tratamento adequado de erros, em vez de usar o recurso (quebrado pelo design IMO) set -e. Realmente não se aplica aqui. O OP deseja sair do script após 5 tentativas fracassadas de executar o comando.
Stéphane Chazelas
11
@ StéphaneChazelas Não vou discutir com você sobre se está quebrado, tenho certeza que você está certo. No entanto, o OP perguntou "Como posso escrever um shell script que sai, se uma parte falhar?", O que faz você pensar que é sair depois de cinco falhas?
terdon
porque não consigo pensar em outra maneira de interpretar a questão.
Stéphane Chazelas
11
@ StéphaneChazelas você pode estar certo. Eu o interpretei literalmente: como o script inteiro pode sair se alguma parte dele falhar. E set -eé a única maneira que sei fazer isso.
terdon
Nesse snipet de script, os comandos que podem ser acionados set -esão sleep( breaksendo um built-in especial, o script pode sair com falha na maioria dos shells, os comandos na ifesquerda ou à esquerda de &&não são afetados set -e, n=...pode falhar se nfor somente leitura, mas depois que sairia do script set -etambém), de modo que a interpretação parece bastante improvável. Concordo que a pergunta está mal formulada.
Stéphane Chazelas
17

Você pode sair de um script em qualquer lugar usando a palavra-chave exit. Você também pode especificar um código de saída para indicar a outros programas que ou como o seu script falhou, por exemplo, exit 1ou exit 2etc. (por convenção, o código de saída 0 é bem-sucedido e qualquer coisa maior que 0 significa falha; no entanto, também por convenção, exit códigos acima de 127 são reservados para terminação anormal (por exemplo, por um sinal)).

A construção genérica para sair em caso de falha é

if [ failure condition ]; then
    exit n
fi

com adequado failure conditione n. Mas em cenários específicos, você pode proceder de maneira diferente. Agora, no seu caso, interpreto sua pergunta de que, se alguma das cinco invocações gksufalhar, você pretende sair. Uma maneira é usar uma função como esta

function try_command {
    for i in 1 2 3 4 5 ; do
        if gksu command ; then
            return 0
        fi
    fi
    exit 1
}

e, em seguida, chame o loop por try_command.

Existem (mais) maneiras avançadas ou sofisticadas de como resolver sua pergunta. No entanto, a solução acima é mais acessível para iniciantes do que, digamos, a solução da Stephane.

contra-modo
fonte
10
attempt=0
until gksu command; do
  attempt=$((attempt + 1))
  if [ "$attempt" -gt 5 ]; then
    exit 1
  fi
done

exitsai do script, a menos que seja chamado em um subshell. Se essa parte do script está em uma subshell, por exemplo, porque é dentro (...)ou $(...)ou parte de uma linha de tubo, então ele só vai sair que subshell .

Nesse caso, se você deseja que o script saia além do subshell, precisará chamar exitesse subshell.

Por exemplo, aqui com 2 níveis aninhados de subshells:

(
  life=hard
  output=$(
    echo blah
    [ "$life" = easy ] || exit 1 # exit subshell
    echo blih not run
  ) || exit # if the subshell exits with a non-zero exit status,
            # exit as well with the same exit status

  echo not run either
) || exit # if the subshell exits with a non-zero exit status,
          # exit as well with the same exit status

Pode se tornar mais complicado se o subshell fizer parte de um pipeline. bashtem um especial $PIPESTATUSarray, semelhante ao zshde $pipestatusum que pode ajudá-lo aqui:

{
   echo foo
   exit 1
   echo bar
} | wc -c
subshell_ret=${PIPESTATUS[0]}
if [ "$subshell_ret" -ne 0 ]; then
  exit "$subshell_ret"
fi
Stéphane Chazelas
fonte
3

A armadilha executará uma ação ao receber um sinal.

trap "echo EXIT;  exit" 0
trap "echo HUP;   exit" 1
trap "echo CTL-C; exit" 2
trap "echo QUIT;  exit" 3
trap "echo ERR;   exit" ERR
n=0
until [ $n -ge 5 ]
do
  n=$[$n+1]
  echo $n
  sleep 3
done

Execute isso e deixe sair normalmente. Prende no sinal 0.

EXIT

Execute-o novamente e interrompa com ^ C. Ele intercepta o sinal 2 e o sinal 0.

CTL-C
EXIT

Um status de saída diferente de zero será interceptado no ERR

ERR
EXIT
RobP
fonte