Thoát khỏi tập lệnh shell với các vòng lặp lồng nhau


11

Tôi có một tập lệnh shell với các vòng lặp lồng nhau và chỉ phát hiện ra rằng "exit" không thực sự thoát khỏi tập lệnh, mà chỉ có vòng lặp hiện tại. Có cách nào khác để thoát hoàn toàn tập lệnh trong một điều kiện lỗi nhất định không?

Tôi không muốn sử dụng "set -e", vì có lỗi chấp nhận được và nó sẽ yêu cầu viết lại quá nhiều.

Ngay bây giờ, tôi đang sử dụng kill để tự hủy tiến trình, nhưng có vẻ như có một cách tốt hơn để làm điều này.


1
Bạn có nghĩa là "thoát" không thực sự thoát khỏi tập lệnh? Nó làm, chỉ cần cố gắng bash -c 'for x in y z; do exit; done; echo "This never gets printed"'.
Chris Down

Bạn nói đúng, thông thường sẽ thoát ra khỏi các vòng lặp lồng nhau, nhưng khi tôi sử dụng thoát, kịch bản của tôi tiếp tục với vòng lặp bên ngoài. Tôi không thể đăng kịch bản.
dùng923487

2
Tại sao bạn không thể viết một kịch bản cho thấy vấn đề và đăng nó ở đây? Điều đó nghe có vẻ không ổn với tôi.
Toby Speight

1
Đây có phải là trường hợp vòng lặp bên trong diễn ra trong một vỏ con trong mã của bạn không?
Toby Speight

@Toby Hầu hết các tập lệnh nằm trong một vỏ phụ cho mục đích ghi nhật ký, nhưng cả hai vòng lặp và phần còn lại của mã đều nằm trong cùng một vỏ phụ.
dùng923487

Câu trả lời:


19

Vấn đề của bạn không phải là các vòng lặp lồng nhau. Đó là một hoặc nhiều vòng lặp bên trong của bạn đang chạy trong một mạng con .

Những công việc này:

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

đầu ra:

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

Điều này trình bày vấn đề bạn đã mô tả:

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

đầu ra:

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

Đây là giải pháp; bạn phải kiểm tra giá trị trả về của các vòng lặp bên trong chạy trong các khung con:

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

Lưu ý kiểm tra: [[ $? != 0 ]] && exit $?

đầu ra:

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!

Chỉnh sửa: Để xác minh xem bạn đang ở đâu, hãy sửa đổi tập lệnh "trả lời" để cho bạn biết ID tiến trình của trình bao hiện tại của bạn là gì. LƯU Ý: Điều này chỉ hoạt động trong 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."

đầu ra:

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

Các biến "i" và "j" mang đến cho bạn phép lịch sự của Fortran. Chúc một ngày tốt lành. :-)


Cả hai vòng lặp bên trong và bên ngoài đang chạy trong cùng một vỏ phụ, vì vậy thoát khỏi vòng lặp bên trong nên thoát khỏi cả hai? Tôi có vấn đề tái tạo vấn đề bản thân mặc dù. Ngay khi tôi loại bỏ hầu hết logic chương trình, vấn đề đã biến mất. Dù sao, bây giờ tôi sẽ đánh dấu đây là câu trả lời, vì có khả năng nó phải làm gì đó với nó.
dùng923487

Sau khi đọc các liên kết bạn cung cấp, tôi nghĩ rằng tôi đã tìm thấy vấn đề. Trong vòng lặp bên ngoài tôi đang thực hiện "tập tin mèo | trong khi đọc dòng". Đường ống tạo ra một vỏ phụ. Tôi không biết điều đó.
dùng923487

@ user923487 - Xem câu trả lời cập nhật của tôi. Nếu bạn có bash 4, bạn có thể lặp lại (hoặc printf, nếu bạn hiện đại) pid subshell và xác minh xem bạn có ở trong subshell hay không. Để xem phiên bản bash của bạn, gõ bash --versionvào dòng lệnh.
Mike S

Rất hữu ích. Cảm ơn! Mặc dù tôi phải nói "killall scriptname.sh" dường như là cách đơn giản nhất để giải quyết vấn đề này.
dùng923487

2

Một câu trả lời trước đó cho thấy sử dụng [[ $? != 0 ]] && exit $?tuy nhiên điều này sẽ không khá làm việc như mong đợi, bởi vì các [[ $? != 0 ]]thử nghiệm sẽ thiết lập lại $?trở về zero, có nghĩa là mặc dù kịch bản sẽ sớm thoát như mong đợi, nó sẽ luôn luôn thoát với mã 0 (như không dự kiến) . Ngoài ra, tốt hơn là sử dụng -nekiểm tra so sánh số, thay vì !=kiểm tra so sánh chuỗi. Vì vậy, IMHO một giải pháp tốt hơn là sử dụng:

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

vì điều đó sẽ đảm bảo rằng mã thoát thực tế được đặt chính xác.


1

Bạn có thể sử dụng break.

Từ help break:

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

Vì vậy, để thoát khỏi ba vòng lặp bao quanh, tức là nếu bạn có hai vòng lặp lồng nhau trong vòng lặp chính, hãy sử dụng điều này để thoát khỏi tất cả các vòng lặp:

break 3

Có nhiều mã hơn sau các vòng lặp, vì vậy việc thoát ra khỏi những thứ đó là không đủ.
dùng923487

1
mát thx! ví dụfor((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Sức mạnh của Bảo Bình

0

exit không chấm dứt toàn bộ vỏ hoặc vỏ phụ hiện tại:

$ 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.