Como posso obter esse script para sair com erro com base no resultado do loop for?


13

Eu tenho um script bash que usa set -o errexitpara que, por erro, o script inteiro saia no ponto de falha.
O script executa um curlcomando que às vezes falha ao recuperar o arquivo pretendido - no entanto, quando isso ocorre, o script não falha ao sair.

Eu adicionei um forloop para

  1. pausar por alguns segundos e tente novamente o curlcomando
  2. use falsena parte inferior do loop for para definir um status de saída 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 curlcomando falha, o loop tenta novamente o comando cinco vezes - se todas as tentativas forem malsucedidas, o loop for termina e o script principal é retomado - em vez de acionar o comando errexit.
Como posso obter o script inteiro para sair se esta curldeclaração falhar?

Respostas:


18

Substituir:

done

com:

done || exit 1

Isso fará com que o código saia se o forloop sair com um código de saída diferente de zero.

Como um ponto de trivialidade, o 1in exit 1não é necessário. Um exitcomando simples sairia com o status de saída do último comando executado, que seria false(código = 1) se o download falhar. Se o download for bem-sucedido, o código de saída do loop é o código de saída do echocomando. echonormalmente sai com o código = 0, sinal de sucesso. Nesse caso, o ||não dispara e o exitcomando não é executado.

Por fim, observe que set -o errexitpode haver muitas surpresas. Para uma discussão sobre seus prós e contras, consulte a FAQ 105 de Greg .

Documentação

De man bash:

para ((expr1; expr2; expr3)); faça a lista; 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, a lista é 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 na lista que é executado ou falso se alguma das expressões for inválida. [Enfase adicionada]


Você acha que seria uma boa idéia colocar trueantes da declaração de interrupção explícita e garantir o valor de saída do loop?
RobertL

1
Eu acho que explícito é melhor do que implícito . Foi por isso que escrevi exit 1quando simplesmente exitfuncionaria. É, porém, uma questão de estilo e outros podem ter suas próprias opiniões.
John1024

1
funciona bem! obrigado :) pessoalmente, eu leria exitcomo uma saída simples - que encerra o script por si só. exit 1 leria para mim como um "sinal" para algum outro processo (ou seja errexit) - que ele deveria finalizar o script com base no "resultado" de exit 1. - então eu ter ido com exitmas graças para explicação
the_velour_fog

1
Se o seu script estiver saindo devido a uma condição de erro, você deve ligar exit 1. Isso não afeta errexitnada. Simplesmente informa ao programa de chamada que algo deu errado. O falsecomando contém uma declaração: exit(1). 99,9% dos comandos Unix retornam 0 em caso de sucesso e diferente de zero em caso de erro. O seu também deveria.
RobertL

2

Se você errexitconfigurou, a falseinstrução deve fazer com que o script saia imediatamente. A mesma coisa se o curlcomando falhou.

Seu exemplo de script, conforme escrito, deve sair após a primeira curlfalha de comando na primeira vez em que chama falsese errexit estiver definido.

Para ver como funciona (eu uso a abreviação -ede para definir errexit:

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

Portanto, se o curlcomando executar mais de uma vez, esse script não foi errexitdefinido.


1
set -eé mais sutil que isso. Ele não será fechado após o primeiro comando com falha em um loop. Você pode provar isso executando (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )e observando que o código é executado falsequatro vezes. Para mais informações set -e, consulte o FAQ de Greg # 105 .
John1024

@ John1024 Obrigado. Este está descendo e descendo.
RobertL

@ John1024 Mas acho que as evidências ainda errexitnão foram definidas. Por favor, aplique a lógica ao script na pergunta. Execute isto: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Sim, testar valores de retorno com if while || &&etc não dispara errexit. O script original não apresentou ||o loop for.
RobertL

Acabei de notar que não havia mostrado o set -o errexitcomando no meu código de exemplo, o adicionei agora - e para mim não foi um erro ao sair conforme o esperado. Eu precisava manter o falsecomo o último comando no loop for, depois fechar o loop com done || exit [1]- então funcionou muito bem!
the_velour_fog

@RobertL Entendo o seu ponto.
John1024

1

set -o errexit pode ser complicado em loops e sub-conchas, porque você precisa voltar ao processo.

Quebrar um loop (mesmo em operação normal) é considerado uma má prática. Você pode me chamar de velha escola para preferir um loop while em vez de um loop for por 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

0

Se errexitestiver definido e o curlcomando falhar, o script será encerrado logo após o comando curl com falha. No manual do bash, não há dica que set -eignore qualquer status de retorno com falha de um único em um comando composto. Este seria apenas o caso se o comando composto for executado em um contexto onde set -eé ignorado.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Experimente um exemplo ligeiramente adaptado publicado por RobertL. Isso para na primeira iteração logo após o comando false:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )

0

Você pode simplesmente adicionar a opção --fail ao comando curl, isso resolverá seu problema, o script falhará e sairá com erro se o comando curl falhar, se muito útil também ao usar curl no pipeline de jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.