Jak sprawić, by ten skrypt wyświetlał błąd wyjścia na podstawie wyniku pętli for?

13

Mam skrypt bash, który używa, set -o errexitwięc po błędzie cały skrypt kończy się w momencie awarii.
Skrypt uruchamia curlpolecenie, które czasami nie może pobrać zamierzonego pliku - jednak gdy to nastąpi, skrypt nie kończy błędu.

Dodałem forpętlę do

  1. zatrzymaj się na kilka sekund, a następnie ponów curlkomendę
  2. użyj falsena dole pętli for, aby zdefiniować domyślny niezerowy status wyjścia - jeśli polecenie curl się powiedzie - pętla się zepsuje, a status wyjścia ostatniego polecenia powinien wynosić 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 .....

Problem polega na tym, że gdy curlpolecenie się nie powiedzie, pętla ponawia polecenie pięć razy - jeśli wszystkie próby zakończą się niepowodzeniem, pętla for kończy się, a skrypt główny jest wznawiany - zamiast wyzwalania errexit.
Jak sprawić, by cały skrypt zakończył działanie, jeśli curlinstrukcja się nie powiedzie?

the_velour_fog
źródło

Odpowiedzi:

18

Zastąpić:

done

z:

done || exit 1

Spowoduje to zamknięcie kodu, jeśli forpętla zakończy działanie z niezerowym kodem wyjścia.

Jako punkt ciekawostki, 1w exit 1nie jest potrzebne. Zwykłe exitpolecenie zakończyło się ze statusem zakończenia ostatniego wykonanego polecenia, które byłoby false(kod = 1), jeśli pobieranie się nie powiedzie. Jeśli pobieranie się powiedzie, kod wyjścia pętli jest kodem wyjścia echopolecenia. echonormalnie kończy się kodem = 0, co oznacza sukces. W takim przypadku polecenie ||nie zostanie uruchomione, a exitpolecenie nie zostanie wykonane.

Na koniec zauważ, że set -o errexitmoże być pełen niespodzianek. Omówienie zalet i wad można znaleźć w FAQ Grega nr 105 .

Dokumentacja

Od man bash:

dla ((wyrażenie 1; wyrażenie 2; wyrażenie 3)); zrobić listę; zrobione
Po pierwsze, wyrażenie arytmetyczne expr1 jest oceniane zgodnie z zasadami opisanymi poniżej w części OCENA ARYTMETYCZNA. Wyrażenie arytmetyczne expr2 jest następnie oceniane wielokrotnie, aż do zera. Za każdym razem, gdy wyrażenie expr2 ma wartość niezerową, lista jest wykonywana i obliczane jest wyrażenie arytmetyczne expr3. Jeśli jakieś wyrażenie zostanie pominięte, zachowuje się tak, jakby miało wartość 1. Zwracana wartość to status wyjścia ostatniego polecenia na liście, które jest wykonywane, lub fałsz, jeśli którekolwiek z wyrażeń jest nieprawidłowe. [Podkreślenie dodane]

John1024
źródło
Czy uważasz, że dobrym pomysłem byłoby umieszczenie trueprzed instrukcją break, aby była jawna i zapewniła wartość wyjściową pętli?
RobertL,
1
Myślę, że wyraźne jest lepsze niż dorozumiane . Dlatego napisałem, exit 1kiedy po prostu exitzadziałałoby. Jest to jednak kwestia stylu, a inni mogą mieć własne opinie.
John1024,
1
działa ładnie! dzięki :) osobiście czytałbym exitjako zwykłe wyjście - to samo kończy skrypt. exit 1 czytałby mi jako „sygnał” do jakiegoś innego procesu (tj. errexit) - że powinien on zakończyć skrypt na podstawie „wyniku” exit 1. - więc poszedłem z, exitale dziękuję za wyjaśnienie
the_velour_fog
1
Jeśli skrypt kończy pracę z powodu błędu, powinieneś zadzwonić exit 1. To w ogóle nie wpływa errexit. Mówi jedynie programowi wywołującemu, że coś poszło nie tak. falsePolecenie zawiera jedno stwierdzenie: exit(1). 99,9% poleceń Uniksa zwraca 0 w przypadku powodzenia i niezerowe w przypadku błędu. Twoje też powinny.
RobertL,
2

Jeśli errexitustawiłeś, to falseinstrukcja powinna spowodować natychmiastowe zakończenie skryptu. To samo, jeśli curlpolecenie się nie powiedzie.

Twój przykładowy skrypt, jak napisano, powinien wyjść po curlniepowodzeniu pierwszego polecenia przy pierwszym wywołaniu, falsejeśli ustawiony jest errexit.

Aby zobaczyć, jak to działa (używam skrótu, -eaby ustawić errexit:

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

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

Jeśli więc curlpolecenie zostanie wykonane więcej niż raz, ten skrypt nie zostanie errexitustawiony.

RobertL
źródło
1
set -ejest bardziej subtelny. To będzie nie wyjść po pierwszej nieudanej polecenia w pętli. Możesz to sobie udowodnić, uruchamiając (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )i zauważając, że kod działa falsecztery razy. Aby uzyskać więcej informacji set -e, zobacz Greg's FAQ # 105 .
John1024,
@ John1024 Dzięki. Ten idzie w dół w dół i na zewnątrz.
RobertL,
@ John1024 Ale chyba wciąż errexitnie ma dowodów . Zastosuj logikę do skryptu w pytaniu. Uruchom to: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) tak testowanie zwracanych wartości za pomocą if while || &&etc nie wyzwala errexit. Oryginalny skrypt nie zawierał ||pętli for.
RobertL,
Właśnie zauważyłem, że nie pokazałem set -o errexitpolecenia w moim przykładowym kodzie, dodałem je teraz - i dla mnie nie było błędu wyjścia zgodnie z oczekiwaniami. Musiałem zachować falseostatnią komendę w pętli for, a następnie zamknąć pętlę za pomocą done || exit [1]- wtedy działało to ładnie!
the_velour_fog
@RobertL Widzę twój punkt widzenia.
John1024,
1

set -o errexit może być trudne w pętlach i podpowłokach, ponieważ musisz przejść z powrotem przez proces.

Zerwanie pętli (nawet podczas normalnej pracy) jest uważane za złą praktykę. Możesz zadzwonić do mnie oldschool, że wolę pętlę while zamiast pętli for dla dwóch warunków, ale uważam, że lepiej jest przeczytać:

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
rexkogitans
źródło
0

Jeśli errexitjest ustawiony, a curlpolecenie nie powiedzie się, skrypt kończy się zaraz po nieudanym poleceniu curl. W podręczniku bash nie ma podpowiedzi, która set -eignoruje wszelkie nieudane zwroty pojedynczego polecenia złożonego. Byłoby tak tylko w przypadku, gdy polecenie złożone jest wykonywane w kontekście, w którym set -ejest ignorowane.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Spróbuj nieco zaadaptowanego przykładu opublikowanego przez RobertL. To kończy się przy pierwszej iteracji zaraz po fałszywym poleceniu:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )
G32RW
źródło
0

Możesz po prostu dodać opcję --fail do polecenia curl, to rozwiąże problem, skrypt zakończy się niepowodzeniem i zakończy działanie po błędzie, jeśli polecenie curl się nie powiedzie, jeśli jest to bardzo przydatne, gdy używasz curl w potoku Jenkinsa:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
DevOps-Eng
źródło