Keluar dari skrip shell dengan loop bersarang

11

Saya memiliki skrip shell dengan loop bersarang dan baru tahu bahwa "keluar" tidak benar-benar keluar dari skrip, tetapi hanya loop saat ini. Apakah ada cara lain untuk sepenuhnya keluar dari skrip pada kondisi kesalahan tertentu?

Saya tidak ingin menggunakan "set -e", karena ada kesalahan yang dapat diterima dan itu akan membutuhkan penulisan ulang terlalu banyak.

Saat ini, saya menggunakan kill untuk mematikan proses secara manual, tetapi sepertinya harus ada cara yang lebih baik untuk melakukan ini.

pengguna923487
sumber
1
Apa maksud Anda bahwa "keluar" tidak benar-benar keluar dari skrip? Ya, coba saja bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Chris Down
Anda benar, biasanya harus keluar dari loop bersarang, tetapi ketika saya menggunakan keluar skrip saya melanjutkan dengan loop luar. Saya tidak dapat memposting skrip.
user923487
2
Mengapa Anda tidak dapat menulis skrip yang menunjukkan masalah dan mempostingnya di sini? Kedengarannya tidak mungkin bagi saya.
Toby Speight
1
Apakah ini kasus bahwa loop dalam berlangsung di sub-shell dalam kode Anda?
Toby Speight
@Toby Sebagian besar skrip berada di sub shell untuk tujuan logging, tetapi kedua loop dan sisa kode berada di sub shell yang sama.
user923487

Jawaban:

19

Masalah Anda bukan loop bersarang, per se. Itu satu atau lebih dari loop batin Anda berjalan dalam subkulit .

Ini bekerja:

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

keluaran:

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

Ini menyajikan masalah yang telah Anda jelaskan:

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

keluaran:

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

Inilah solusinya; Anda harus menguji nilai balik loop internal yang berjalan dalam subkulit:

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

Perhatikan tesnya: [[ $? != 0 ]] && exit $?

keluaran:

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!

Sunting: Untuk memverifikasi Anda berada di subkulit apa, modifikasi skrip "jawab" untuk memberi tahu Anda apa proses ID dari shell Anda saat ini. CATATAN: Ini hanya berfungsi di 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."

keluaran:

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

Variabel "i" dan "j" dibawa kepada Anda dari Fortran. Semoga harimu menyenangkan. :-)

Mike S
sumber
Baik loop dalam dan luar berjalan di sub shell yang sama, jadi keluar dari dalam harus keluar keduanya? Saya memang kesulitan mereproduksi masalah sendiri. Segera setelah saya menghapus sebagian besar logika program, masalahnya hilang. Bagaimanapun, saya akan menandai ini sebagai jawaban untuk saat ini, karena kemungkinan harus melakukan sesuatu dengannya.
user923487
Setelah membaca tautan yang Anda berikan, saya pikir saya menemukan masalahnya. Di lingkaran luar saya sedang melakukan "file cat | sambil membaca baris". Perpipaan membuat sub shell. Saya tidak tahu itu.
user923487
@ user923487 - Lihat jawaban saya yang diperbarui. Jika Anda memiliki bash 4, Anda dapat menggema (atau mencetak, jika Anda modern) dengan subkulit pid dan memverifikasi apakah Anda berada dalam subkulit atau tidak. Untuk melihat versi bash Anda, ketikkan bash --versionpada baris perintah.
Mike S
Sangat membantu. Terima kasih! Meskipun saya harus mengatakan "killall scriptname.sh" tampaknya menjadi cara paling mudah untuk menyelesaikan ini.
user923487
2

Jawaban sebelumnya menyarankan menggunakan [[ $? != 0 ]] && exit $?namun hal ini tidak akan cukup bekerja seperti yang diharapkan, karena [[ $? != 0 ]]tes akan mengatur ulang $?kembali ke nol, yang berarti bahwa meskipun script akan awal-keluar seperti yang diharapkan, itu akan selalu keluar dengan kode 0 (tidak diharapkan) . Selain itu, akan lebih baik menggunakan -neuji perbandingan numerik, daripada !=tes perbandingan string. Jadi karena itu IMHO solusi yang lebih baik adalah dengan menggunakan:

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

karena itu akan memastikan bahwa kode keluar yang sebenarnya diatur dengan benar.

Lurchman
sumber
Umpan balik yang bagus. Saya sudah memperbaiki kode.
Mike S
1

Anda bisa menggunakannya break.

Dari help break:

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

Jadi untuk keluar dari tiga loop tertutup yaitu jika Anda memiliki dua loop bersarang di dalam loop utama, gunakan ini untuk keluar dari semuanya:

break 3
heemayl
sumber
Ada lebih banyak kode setelah loop, jadi membobolnya saja tidak cukup.
user923487
1
keren thx! contohfor((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 tidak mengakhiri seluruh shell, atau sub-shell saat ini:

$ 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
sumber