
Eu tenho um script bash que usaset -o errexit
para que, em caso de erro, todo o script seja encerrado no ponto de falha.
O script executa um curl
comando que às vezes falha ao recuperar o arquivo pretendido - no entanto, quando isso ocorre, o script não sai com erro.
Eu adicionei umfor
loop para
- pause por alguns segundos e tente novamente
curl
comando - use
false
na parte inferior do loop for para definir um status de saída padrão diferente de zero - se o comando curl for bem-sucedido - o loop será interrompido e o status de saída do último comando deverá ser zero.
#! /bin/bash
set -o errexit
# ...
for (( i=1; i<5; i++ ))
do
echo "attempt number: "$i
curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
if [ -f ~/.vim/autoload/pathogen.vim ]
then
echo "file has been retrieved by curl, so breaking now..."
break;
fi
echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
sleep 5
# exit with non-zero status so main script will errexit
false
done
# rest of script .....
O problema é que quando o curl
comando falha, o loop tenta novamente o comando cinco vezes - se todas as tentativas não forem bem-sucedidas, o loop for termina e o script principal é retomado - em vez de acionar o errexit
.
Como posso fazer com que o script inteiro saia se esta curl
instrução falhar?
Responder1
Substituir:
done
com:
done || exit 1
Isso fará com que o código saia se ofor
loop terminar com um código de saída diferente de zero.
A título de curiosidade, o 1
in exit 1
não é necessário. Um comando simples exit
sairia com o status de saída do último comando executado, que seria false
(código = 1) se o download falhasse. Se o download for bem-sucedido, o código de saída do loop será o código de saída do echo
comando. echo
normalmente sai com código = 0, sinalizando sucesso. Nesse caso, o||
não é acionado e o exit
comando não é executado.
Por último, observe que set -o errexit
pode ser cheio de surpresas. Para uma discussão sobre seus prós e contras, consultePerguntas frequentes de Greg nº 105.
Documentação
De man bash
:
para(( expr1 ; expr2 ; expr3 )) ;fazerlista ; feito
Primeiro, a expressão aritmética expr1 é avaliada de acordo com as regras descritas abaixo em AVALIAÇÃO ARITMÉTICA. A expressão aritmética expr2 é então avaliada repetidamente até ser avaliada como zero. Cada vez que expr2 é avaliado como um valor diferente de zero, list é executada e a expressão aritmética expr3 é avaliada. Se alguma expressão for omitida, ela se comportará como se fosse avaliada como 1. O valor de retorno é o status de saída do último comando da lista executado ou falso se alguma das expressões for inválida.[Enfase adicionada]
Responder2
Se preferir parar o loop na primeira falha, e também evitar set -e
, você pode fazer algo assim:
for i in `seq 1 10`; do
run_command || exit 1;
done
Responder3
Se você tiver errexit
definido, a false
instrução deverá fazer com que o script seja encerrado imediatamente. A mesma coisa se o curl
comando falhar.
Seu script de exemplo, conforme escrito, deve sair após a curl
falha do primeiro comando na primeira vez que ele chamar false
se errexit estiver definido.
Para ver como funciona (eu uso a abreviação -e
de to set errexit
:
$ ( set -e; false; echo still here )
$
$ ( set +e; false; echo still here )
still here
$
Portanto, se o curl
comando for executado mais de uma vez, este script não está errexit
definido.
Responder4
set -o errexit
pode ser complicado em loops e subshells, porque você precisa retornar do processo.
Quebrar um loop (mesmo em operação normal) é considerado uma má prática. Você pode me chamar de antiquado por preferir um loop while em vez de um loop for para duas condições, mas acho melhor ler:
i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
[ $i -eq 1 ] || sleep 5
echo "attempt number: "$i
curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
RET=$?
i=$((i+1))
done
exit $RET