Вихід із сценарію оболонки з вкладеними петлями


11

У мене є скрипт оболонки з вкладеними петлями, і я лише дізнався, що "вихід" насправді не виходить із сценарію, а лише поточний цикл. Чи є інший спосіб повністю вийти зі скрипту за певної умови помилки?

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

Зараз я використовую функцію kill, щоб вручну вбити процес, але, здається, має бути кращий спосіб зробити це.


1
Що ви маєте на увазі, що "вихід" насправді не виходить із сценарію? Так, просто спробуйте bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Кріс Даун

Ви маєте рацію, зазвичай він повинен виходити з вкладених циклів, але коли я використовую вихід, мій сценарій продовжується із зовнішнього циклу. Я не можу опублікувати сценарій.
користувач923487

2
Чому ви не можете написати скрипт, який показує проблему, і опублікувати його тут? Це здається мені малоймовірним.
Toby Speight

1
Чи буває так, що внутрішній цикл відбувається в підколонці вашого коду?
Toby Speight

@Toby Більшість сценаріїв знаходяться в додатковій оболонці для цілей реєстрації, але обидва циклу та решта коду знаходяться в одній підкожній оболонці.
користувач923487

Відповіді:


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!

Редагувати. Щоб перевірити, в якій підпайці ви перебуваєте, змініть сценарій "відповідь", щоб повідомити, що таке ідентифікатор процесу вашої поточної оболонки. ПРИМІТКА. Це працює лише в базі 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. Гарного дня. :-)


І внутрішня, і зовнішня петля працюють в одній підкожці, тому вихід із внутрішньої повинен вийти з обох? У мене ж проблеми з відтворенням проблеми самі. Як тільки я видаляю більшу частину логіки програми, проблема відпадає. У всякому разі, я зараз це позначу як відповідь, тому що, ймовірно, доведеться щось з цим зробити.
користувач923487

Прочитавши надане вами посилання, я думаю, що знайшов проблему. У зовнішньому циклі я роблю "файл котів | під час читання рядка". Трубопровід створює додаткову оболонку. Я цього не знав.
user923487

@ user923487 - Дивіться мою оновлену відповідь. Якщо у вас є bash 4, ви можете повторити (або printf, якщо ви сучасні), піде підрозділ підключення і перевірити, чи перебуваєте ви в додатковій оболонці чи ні. Щоб побачити версію bash, введіть bash --versionу командному рядку.
Mike S

Дуже корисний. Спасибі! Хоча я маю сказати, що "killall scriptname.sh" здається найбільш простим способом вирішити це.
користувач923487

2

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

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

як це буде гарантувати , що фактичний код виходу встановлено правильно.


Хороший відгук. Я виправив код.
Майк S

1

Можна використовувати break.

Від help break:

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

Тож для виходу з трьох циклів, що укладаються, тобто якщо у вас є дві вкладені петлі всередині основної, використовуйте це для виходу з усіх них:

break 3

Після циклів є більше коду, тому виходу з них одних лише недостатньо.
користувач923487

1
класний THX! приклад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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.