Wie kann ich dieses Skript basierend auf dem Ergebnis der for-Schleife auf Fehler-Exit bringen?

13

Ich habe ein Bash-Skript, das verwendet, set -o errexitdamit bei einem Fehler das gesamte Skript zum Zeitpunkt des Fehlers beendet wird.
Das Skript führt einen curlBefehl aus, mit dem die gewünschte Datei manchmal nicht abgerufen werden kann. In diesem Fall tritt jedoch kein Fehler beim Beenden des Skripts auf.

Ich habe eine forSchleife hinzugefügt

  1. Machen Sie eine kurze Pause und wiederholen Sie den curlBefehl
  2. Verwenden Sie falseam Ende der for-Schleife, um einen Standard-Ausgangsstatus ungleich Null zu definieren. Wenn der curl-Befehl erfolgreich ausgeführt wurde, wird die Schleife unterbrochen und der Ausgangsstatus des letzten Befehls sollte Null sein.
#! /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 .....

Das Problem ist, wenn der curlBefehl fehlschlägt, die Schleife den Befehl fünfmal wiederholt - wenn alle Versuche erfolglos sind, wird die for-Schleife beendet und das Hauptskript wird fortgesetzt -, anstatt den Befehl auszulösen errexit.
Wie kann ich das gesamte Skript beenden, wenn diese curlAnweisung fehlschlägt?

the_velour_fog
quelle

Antworten:

18

Ersetzen:

done

mit:

done || exit 1

Dadurch wird der Code beendet, wenn die forSchleife mit einem Exit-Code ungleich Null beendet wird.

Als Punkt von Lappalien, die 1in exit 1nicht benötigt wird . Ein einfacher exitBefehl würde mit dem Beendigungsstatus des zuletzt ausgeführten Befehls beendet, der false(Code = 1) wäre, wenn der Download fehlschlägt. Wenn der Download erfolgreich ist, ist der Exit-Code der Schleife der Exit-Code des echoBefehls. echoBeendet normalerweise mit Code = 0, signalisiert Erfolg. In diesem Fall wird das ||nicht ausgelöst und der exitBefehl wird nicht ausgeführt.

Beachten Sie zum Schluss, dass set -o errexites viele Überraschungen geben kann. Eine Diskussion der Vor- und Nachteile finden Sie in Gregs FAQ Nr. 105 .

Dokumentation

Von man bash:

für ((Ausdruck1; Ausdruck2; Ausdruck3)); do - Liste; done
Zunächst wird der arithmetische Ausdruck expr1 wird die Regeln ausgewertet nach Anspruch nachstehend unter Arithmetische Auswertung. Der arithmetische Ausdruck expr2 wird dann wiederholt ausgewertet, bis er null ergibt. Jedes Mal, wenn expr2 einen Wert ungleich Null ergibt, wird list ausgeführt und der arithmetische Ausdruck expr3 ausgewertet. Wenn ein Ausdruck weggelassen wird, verhält er sich so, als würde er zu 1 ausgewertet. Der Rückgabewert ist der Beendigungsstatus des letzten ausgeführten Befehls in der Liste oder false, wenn einer der Ausdrücke ungültig ist. [Betonung hinzugefügt]

John1024
quelle
Halten Sie es für eine gute Idee, truedie break-Anweisung explizit voranzustellen und den Exit-Wert der Schleife sicherzustellen?
RobertL
1
Ich denke, dass explizit besser ist als implizit . Deshalb habe ich geschrieben, exit 1als es einfach geklappt exithätte. Es ist jedoch eine Frage des Stils und andere können ihre eigene Meinung haben.
John1024
1
funktioniert gut! danke :) persönlich würde ich das exitals normales beenden lesen - das beendet das script von selbst. exit 1 würde mir als "signal" für einen anderen prozess (dh errexit) vorlesen, dass er das script basierend auf dem "ergebnis" von beenden soll exit 1. - also bin ich exit
mitgegangen
1
Wenn Ihr Skript aufgrund einer Fehlerbedingung beendet wird, sollten Sie aufrufen exit 1. Das wirkt sich überhaupt nicht aus errexit. Es teilt dem aufrufenden Programm lediglich mit, dass ein Fehler aufgetreten ist. Der falseBefehl enthält eine Anweisung: exit(1). 99,9% der Unix-Befehle geben bei Erfolg 0 und bei Fehlern ungleich Null zurück. Deines sollte auch.
RobertL
2

Wenn Sie festgelegt haben errexit, sollte die falseAnweisung das Skript sofort beenden. Dasselbe gilt, wenn der curlBefehl fehlgeschlagen ist.

Ihr Beispielskript sollte, wie geschrieben, nach dem ersten curlfehlgeschlagenen Befehl beim ersten falseAufruf beendet werden, wenn errexit gesetzt ist.

Um zu sehen, wie es funktioniert (ich benutze die Abkürzung -efür errexit:

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

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

Wenn der curlBefehl also mehrmals ausgeführt wird, hat dieses Skript keineerrexit festgelegt.

RobertL
quelle
1
set -eist subtiler als das. Es wird nicht nach dem ersten fehlgeschlagenen Befehl in einer Schleife beendet. Sie können sich das selbst beweisen, (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )indem Sie ausführen und feststellen, dass der Code falseviermal ausgeführt wird. Weitere Informationen set -efinden Sie in Gregs FAQ Nr. 105 .
John1024
@ John1024 Danke. Das hier geht runter runter und raus.
RobertL
@ John1024 Aber ich denke der Beweis ist immer noch, dass errexitnicht gesetzt wurde. Bitte wenden Sie die Logik auf das Skript in der Frage an. Führen Sie Folgendes aus : (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Ja. Das Testen von Rückgabewerten mit if while || &&etc löst kein Errexit aus. Das ursprüngliche Skript hat ||die for-Schleife nicht ausgeführt.
RobertL
Mir ist nur aufgefallen, dass ich den set -o errexitBefehl in meinem Beispielcode nicht angezeigt habe, ich habe ihn jetzt hinzugefügt - und für mich war es kein Fehler, wie erwartet zu beenden. Ich musste den falseals letzten Befehl in der for-Schleife behalten , dann die Schleife schließen mit done || exit [1]- dann hat es gut funktioniert!
the_velour_fog
@ RobertL Ich verstehe deinen Standpunkt.
John1024
1

set -o errexit kann in Schleifen und Unterschalen schwierig sein, weil Sie den Weg zurück aus dem Prozess passieren müssen.

Das Unterbrechen einer Schleife (auch im normalen Betrieb) wird als schlechte Praxis angesehen. Sie können mich Old-School nennen, um eine while-Schleife anstelle einer for-Schleife für zwei Bedingungen zu bevorzugen, aber ich finde es besser zu lesen:

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
quelle
0

Wenn errexitgesetzt ist und der curlBefehl fehlschlägt, wird das Skript direkt nach dem fehlgeschlagenen Einrollbefehl beendet. Im Bash-Handbuch gibt es keinen Hinweis, der den set -efehlgeschlagenen Rückgabestatus eines einzelnen Befehls in einem zusammengesetzten Befehl ignoriert. Dies ist nur der Fall, wenn der zusammengesetzte Befehl in einem Kontext ausgeführt wird, in dem er set -eignoriert wird.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Probieren Sie ein leicht angepasstes Beispiel von RobertL. Dies stoppt bei der ersten Wiederholung direkt nach dem falschen Befehl:

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

Sie können einfach die Option --fail zum Befehl curl hinzufügen. Dadurch wird Ihr Problem behoben. Das Skript schlägt fehl und wird bei einem Fehler beendet, wenn der Befehl curl fehlschlägt.

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
DevOps-Eng
quelle