Beenden eines Shell-Skripts mit verschachtelten Schleifen

11

Ich habe ein Shell-Skript mit verschachtelten Schleifen und habe gerade herausgefunden, dass "exit" das Skript nicht wirklich beendet, sondern nur die aktuelle Schleife. Gibt es eine andere Möglichkeit, das Skript unter bestimmten Fehlerbedingungen vollständig zu beenden?

Ich möchte "set -e" nicht verwenden, da es akzeptable Fehler gibt und es zu viel Umschreiben erfordern würde.

Im Moment verwende ich kill, um den Prozess manuell abzubrechen, aber es scheint, dass es einen besseren Weg geben sollte, dies zu tun.

user923487
quelle
1
Was meinst du damit, dass "exit" das Skript nicht wirklich beendet? Versuchen Sie es einfach bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Chris Down
Sie haben Recht, es sollte normalerweise aus verschachtelten Schleifen beendet werden, aber wenn ich exit verwende, fährt mein Skript mit der äußeren Schleife fort. Ich kann das Skript nicht posten.
user923487
2
Warum kannst du kein Skript schreiben, das das Problem zeigt, und es hier posten? Das klingt für mich unwahrscheinlich.
Toby Speight
1
Ist es so, dass die innere Schleife in einer Sub-Shell in Ihrem Code stattfindet?
Toby Speight
@Toby Der größte Teil des Skripts befindet sich zu Protokollierungszwecken in einer Sub-Shell, aber beide Schleifen und der Rest des Codes befinden sich in derselben Sub-Shell.
user923487

Antworten:

19

Ihr Problem sind an sich keine verschachtelten Schleifen. Es ist so, dass eine oder mehrere Ihrer inneren Schleifen in einer Unterschale ausgeführt werden .

Das funktioniert:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        for j in $(seq 1 10) ; do
                echo j $j
                sleep 1
                [[ $j = 3 ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done
echo "After all the loops."

Ausgabe:

i 1
j 1
j 2
j 3
I've had enough!

Dies stellt das von Ihnen beschriebene Problem dar:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done    
echo "After all the loops."

Ausgabe:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 2
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 3
LINE root:x:0:0:root:/root:/bin/bash
(...etc...)

Hier ist die Lösung; Sie müssen den Rückgabewert von inneren Schleifen testen, die in Unterschalen ausgeführt werden:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && exit $?
        echo "After the j loop."
done
echo "After all the loops."

Beachten Sie den Test: [[ $? != 0 ]] && exit $?

Ausgabe:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!

Bearbeiten: Um zu überprüfen, in welcher Subshell Sie sich befinden, ändern Sie das Antwortskript, um die Prozess-ID Ihrer aktuellen Shell anzugeben. HINWEIS: Dies funktioniert nur in Bash 4:

#!/bin/bash

for i in $(seq 1 100); do
        echo pid $BASHPID i $i
        cat /etc/passwd | while read line; do
                echo pid $BASHPID LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && echo pid $BASHPID && exit $?
        echo "After the j loop."
done
echo "After all the loops."

Ausgabe:

pid 31793 i 1
pid 31796 LINE root:x:0:0:root:/root:/bin/bash
pid 31796 LINE bin:x:1:1:bin:/bin:/sbin/nologin
pid 31796 LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
pid 31793

Die Variablen "i" und "j" wurden Ihnen mit freundlicher Genehmigung von Fortran zur Verfügung gestellt. Einen schönen Tag noch. :-)

Mike S.
quelle
Sowohl die innere als auch die äußere Schleife laufen in derselben Unterschale. Wenn Sie also die innere Schleife verlassen, sollten Sie beide verlassen? Ich habe jedoch Probleme, das Problem selbst zu reproduzieren. Sobald ich den größten Teil der Programmlogik entferne, ist das Problem behoben. Wie auch immer, ich werde dies vorerst als Antwort markieren, da es wahrscheinlich etwas damit zu tun hat.
user923487
Nachdem ich den von Ihnen angegebenen Link gelesen habe, habe ich das Problem gefunden. In der äußeren Schleife mache ich "cat file | while read line". Durch Rohrleitungen wird eine Unterschale erstellt. Das wusste ich nicht.
user923487
@ user923487 - Siehe meine aktualisierte Antwort. Wenn Sie Bash 4 haben, können Sie die Subshell-PID wiederholen (oder printf, wenn Sie modern sind) und überprüfen, ob Sie sich in einer Subshell befinden oder nicht. Geben Sie bash --versionin die Befehlszeile ein, um Ihre Bash-Version anzuzeigen .
Mike S
Sehr hilfreich. Vielen Dank! Obwohl ich sagen muss, dass "killall scriptname.sh" der einfachste Weg ist, dies zu lösen.
user923487
2

Eine frühere Antwort schlägt vor , mit [[ $? != 0 ]] && exit $?jedoch wird dies nicht ganz wie erwartet, da der [[ $? != 0 ]]Test zurückgesetzt wird $?wieder auf Null, was bedeutet , dass , obwohl das Skript wird früh-Ausgang wie erwartet, es wird immer Ausfahrt mit dem Code 0 (wie nicht zu erwarten) . Außerdem ist es besser, den -nenumerischen Vergleichstest als den !=Zeichenfolgenvergleichstest zu verwenden. Daher ist meiner Meinung nach eine bessere Lösung:

err=$?; [[ $err -ne 0 ]] && exit $err

Dadurch wird sichergestellt, dass der tatsächliche Exit-Code korrekt eingestellt ist.

Lurchman
quelle
1

Sie können verwenden break.

Von help break:

Exit a FOR, WHILE or UNTIL loop.  If N is specified, break N enclosing loops.

Wenn Sie also drei umschließende Schleifen verlassen möchten, dh wenn Sie zwei verschachtelte Schleifen in einer Hauptschleife haben, verwenden Sie diese, um alle zu verlassen:

break 3
heemayl
quelle
Nach den Schleifen gibt es mehr Code, daher reicht es nicht aus, nur aus diesen auszubrechen.
user923487
1
cool thx! Beispielfor((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Aquarius Power
0

exit beendet die gesamte Shell oder die aktuelle Sub-Shell:

$ bash -c 'for i in 1 2 3; do for j in 4 5 6; do echo $i; exit 1; echo $j; done; done'
1
$ echo $?
1
Toby Speight
quelle