Wychodzenie ze skryptu powłoki za pomocą zagnieżdżonych pętli

11

Mam skrypt powłoki z zagnieżdżonymi pętlami i właśnie odkryłem, że „exit” tak naprawdę nie kończy skryptu, a jedynie bieżącą pętlę. Czy istnieje inny sposób całkowitego wyjścia ze skryptu w przypadku wystąpienia określonego błędu?

Nie chcę używać „zestawu -e”, ponieważ występują dopuszczalne błędy i wymagałoby to zbyt dużego przepisywania.

W tej chwili używam kill do ręcznego zabicia procesu, ale wydaje się, że powinien istnieć lepszy sposób na zrobienie tego.

użytkownik923487
źródło
1
Co masz na myśli, że „exit” tak naprawdę nie wychodzi ze skryptu? Tak, po prostu spróbuj bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Chris Down,
Masz rację, normalnie powinien wyjść z zagnieżdżonych pętli, ale kiedy używam wyjścia, mój skrypt kontynuuje pracę z zewnętrzną pętlą. Nie mogę opublikować skryptu.
user923487
2
Dlaczego nie możesz napisać skryptu pokazującego problem i opublikować go tutaj? Wydaje mi się to mało prawdopodobne.
Toby Speight
1
Czy to jest tak, że wewnętrzna pętla odbywa się w podpowłoce w twoim kodzie?
Toby Speight
@Toby Większość skryptu znajduje się w podpowłoce do celów logowania, ale obie pętle i reszta kodu znajdują się w tej samej podpowłoce.
user923487

Odpowiedzi:

19

Twoim problemem nie są same zagnieżdżone pętle. Chodzi o to, że jedna lub więcej wewnętrznych pętli działa w podpowłoce .

To działa:

#!/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."

wynik:

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

To przedstawia opisany problem:

#!/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."

wynik:

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...)

Oto rozwiązanie; musisz przetestować zwracaną wartość wewnętrznych pętli, które działają w podpowłokach:

#!/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
        err=$?; [[ $err != 0 ]] && exit $err
        echo "After the j loop."
done
echo "After all the loops."

Uwaga na test: [[ $? != 0 ]] && exit $?

wynik:

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!

Edycja: Aby sprawdzić, w jakiej podpowłoce się znajdujesz, zmodyfikuj skrypt „answer”, aby poinformować Cię, jaki jest identyfikator procesu bieżącej powłoki. UWAGA: Działa to tylko w 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
        err=$?; [[ $err != 0 ]] && echo pid $BASHPID && exit $err
        echo "After the j loop."
done
echo "After all the loops."

wynik:

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

Zmienne „i” i „j” zostały udostępnione dzięki uprzejmości Fortran. Miłego dnia. :-)

Mike S.
źródło
Zarówno wewnętrzna, jak i zewnętrzna pętla działają w tej samej podpowłoce, więc wychodząc z wewnętrznej, należy wyjść z obu? Mam jednak problem z odtworzeniem problemu. Wkrótce po usunięciu większości logiki programu problem zniknął. W każdym razie zaznaczę to jako odpowiedź, ponieważ prawdopodobnie będzie musiało coś z tym zrobić.
user923487
Po przeczytaniu podanego linku myślę, że znalazłem problem. W zewnętrznej pętli wykonuję „plik kota | podczas odczytu linii”. Piping tworzy pod-powłokę. Nie wiedziałem tego
user923487
@ user923487 - Zobacz moją zaktualizowaną odpowiedź. Jeśli masz bash 4, możesz powtórzyć (lub printf, jeśli jesteś nowoczesny) pid podpowłoki i sprawdzić, czy jesteś w podpowłoce, czy nie. Aby zobaczyć wersję bash, wpisz bash --versionw wierszu poleceń.
Mike S
Bardzo pomocny. Dzięki! Chociaż muszę powiedzieć „killall scriptname.sh” wydaje się być najprostszym sposobem na rozwiązanie tego.
user923487,
2

Wcześniejsza odpowiedź sugeruje użycie [[ $? != 0 ]] && exit $?jednak nie będzie to dość pracy zgodnie z oczekiwaniami, ponieważ [[ $? != 0 ]]badanie zostanie zresetowany $?do zera, co oznacza, że chociaż skrypt wczesne wyjście zgodnie z oczekiwaniami, to będzie zawsze wyjście z kodem 0 (jak nie oczekuje) . Lepiej byłoby też użyć -nenumerycznego testu porównawczego niż !=testu porównania łańcuchów. Dlatego IMHO lepszym rozwiązaniem jest użycie:

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

ponieważ zapewni to prawidłowe ustawienie faktycznego kodu wyjścia.

Lurchman
źródło
Dobre opinie Poprawiłem kod.
Mike S
1

Możesz użyć break.

Od help break:

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

Aby wyjść z trzech otaczających pętli, tj. Jeśli masz dwie zagnieżdżone pętle w jednej głównej, użyj tego, aby wyjść ze wszystkich:

break 3
heemayl
źródło
Po pętlach jest więcej kodu, więc wyrwanie się z nich samych nie wystarczy.
user923487
1
super dzięki! przykładfor((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Wodnik Moc
0

exit kończy całą powłokę lub bieżącą podpowłokę:

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