Выход из сценария оболочки с вложенными циклами

11

У меня есть скрипт оболочки с вложенными циклами, и я только что узнал, что «выход» на самом деле не завершает скрипт, а только текущий цикл. Есть ли другой способ полностью выйти из сценария при определенном состоянии ошибки?

Я не хочу использовать "set -e", потому что есть допустимые ошибки, и это потребует слишком много переписывания.

Прямо сейчас я использую kill для ручного уничтожения процесса, но, похоже, должен быть лучший способ сделать это.

user923487
источник
1
Что вы имеете в виду, что «выход» на самом деле не выходит из сценария? Это так, просто попробуйте bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Крис Даун
Вы правы, обычно он должен выходить из вложенных циклов, но когда я использую команду exit, мой сценарий продолжает работу с внешним циклом. Я не могу опубликовать сценарий.
user923487
2
Почему вы не можете написать скрипт, который показывает проблему, и опубликовать ее здесь? Это звучит маловероятно для меня.
Тоби Спейт
1
Это тот случай, когда внутренний цикл происходит в под-оболочке в вашем коде?
Тоби Спейт
@Toby Большая часть сценария находится в вспомогательной оболочке для ведения журнала, но оба цикла и остальной код находятся в одной вложенной оболочке.
user923487

Ответы:

19

Ваша проблема не в вложенных циклах как таковых. Это то, что один или несколько ваших внутренних циклов работает в подоболочке .

Это работает:

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

выход:

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

Это представляет проблему, которую вы описали:

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

выход:

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

Вот решение; Вы должны проверить возвращаемое значение внутренних циклов, которые работают в подоболочках:

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

Обратите внимание на тест: [[ $? != 0 ]] && exit $?

выход:

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!

Редактировать: чтобы проверить, в какой подоболочке вы находитесь, измените скрипт «answer», чтобы сообщить вам идентификатор процесса вашей текущей оболочки. ПРИМЕЧАНИЕ: это работает только в 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."

выход:

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

Переменные «i» и «j» предоставлены вам с разрешения Fortran. Хорошего дня. :-)

Майк С
источник
И внутренний, и внешний цикл выполняются в одной и той же вложенной оболочке, поэтому выход из внутреннего должен завершить оба? У меня есть проблемы с воспроизведением проблемы, хотя сам. Как только я удалю большую часть логики программы, проблема исчезнет. В любом случае, я пока помечу это как ответ, потому что, скорее всего, придется что-то с этим делать.
user923487
Прочитав предоставленную вами ссылку, я думаю, что нашел проблему. Во внешнем цикле я делаю "cat file | while read line". Трубопровод создает субоболочку. Я этого не знал.
user923487
@ user923487 - Смотрите мой обновленный ответ. Если у вас есть bash 4, вы можете повторить (или printf, если вы современный) pid subshell и проверить, находитесь ли вы в subshell или нет. Чтобы увидеть свою версию bash, введите bash --versionв командной строке.
Майк С
Очень полезно. Благодарность! Хотя я должен сказать, что killall scriptname.sh, кажется, самый простой способ решить эту проблему.
user923487
2

Ранее ответ предлагает использовать , [[ $? != 0 ]] && exit $?однако это не совсем работа , как и следовало ожидать, потому что [[ $? != 0 ]]тест будет сброшен $?в ноль, что означает , что , хотя сценарий будет рано выйти , как ожидается, он будет всегда выход с кодом 0 (не ожидается) , Кроме того, было бы лучше использовать -neтест сравнения чисел, а не !=тест сравнения строк. Поэтому, ИМХО, лучшим решением будет использовать:

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

поскольку это гарантирует, что фактический код выхода установлен правильно.

Lurchman
источник
Хороший отзыв. Я исправил код.
Майк С
1

Вы можете использовать break.

От help break:

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

Таким образом, для выхода из трех замкнутых циклов, т.е. если у вас есть два вложенных цикла внутри основного, используйте это для выхода из всех них:

break 3
heemayl
источник
После циклов появляется больше кода, поэтому взломать их одного недостаточно.
user923487
1
круто спасибо! примерfor((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Водолей Сила
0

exit завершает всю оболочку или текущую вложенную оболочку:

$ 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
Тоби Спейт
источник