การออกจากเชลล์สคริปต์ด้วยลูปซ้อนกัน


11

ฉันมีเชลล์สคริปต์ที่มีลูปซ้อนกันและเพิ่งพบว่า "exit" ไม่ได้ออกจากสคริปต์จริงๆ แต่เป็นลูปปัจจุบันเท่านั้น มีวิธีการออกจากสคริปต์อย่างสมบูรณ์ในเงื่อนไขข้อผิดพลาดบางอย่างหรือไม่

ฉันไม่ต้องการใช้ "set -e" เนื่องจากมีข้อผิดพลาดที่ยอมรับได้และจะต้องเขียนใหม่มากเกินไป

ตอนนี้ฉันกำลังใช้ kill เพื่อฆ่ากระบวนการด้วยตนเอง แต่ดูเหมือนว่าควรมีวิธีที่ดีกว่าในการทำเช่นนี้


1
คุณหมายความว่า "ทางออก" ไม่ได้ออกจากสคริปต์จริงๆหรือ bash -c 'for x in y z; do exit; done; echo "This never gets printed"'มันไม่เพียงแค่พยายาม
Chris Down

คุณพูดถูกมันควรจะออกจากลูปซ้อนกัน แต่เมื่อฉันใช้ออกจากสคริปต์ของฉันดำเนินการต่อกับวงรอบนอก ฉันไม่สามารถโพสต์สคริปต์ได้
user923487

2
ทำไมคุณไม่สามารถเขียนสคริปต์ที่แสดงปัญหาและโพสต์ได้ที่นี่ นั่นฟังดูไม่น่าสำหรับฉัน
Toby Speight

1
เป็นกรณีที่วงภายในเกิดขึ้นใน sub-shell ในรหัสของคุณหรือไม่?
Toby Speight

@Toby สคริปต์ส่วนใหญ่อยู่ในเชลล์ย่อยสำหรับวัตถุประสงค์ในการบันทึก แต่ทั้งลูปและโค้ดที่เหลืออยู่ในเชลล์ย่อยเดียวกัน
user923487

คำตอบ:


19

ปัญหาของคุณไม่ใช่ลูปซ้อนกัน นั่นคือการวนซ้ำภายในของคุณอย่างน้อยหนึ่งครั้งกำลังทำงานใน subshell

งานนี้:

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

นี่คือทางออก; คุณต้องทดสอบค่าส่งคืนของลูปภายในที่ทำงานใน subshells:

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

แก้ไข: เพื่อตรวจสอบว่าคุณอยู่ใน subshell ใดให้แก้ไขสคริปต์ "answer" เพื่อบอกคุณว่า ID กระบวนการของเชลล์ปัจจุบันของคุณคืออะไร หมายเหตุ: ใช้งานได้เฉพาะในทุบตี 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 | ขณะอ่านบรรทัด" Piping สร้างเชลล์ย่อย ฉันไม่รู้
user923487

@ user923487 - ดูคำตอบที่อัปเดตของฉัน หากคุณมีทุบตี 4 คุณสามารถ echo (หรือ printf ถ้าคุณทันสมัย) subshell pid และตรวจสอบว่าคุณอยู่ใน subshell หรือไม่ หากต้องการดูเวอร์ชันทุบตีของคุณให้พิมพ์bash --versionบนบรรทัดคำสั่ง
Mike S

มีประโยชน์มาก ขอบคุณ! แม้ว่าฉันจะต้องพูดว่า "killall scriptname.sh" ดูเหมือนจะเป็นวิธีที่ตรงไปตรงมาที่สุดในการแก้ปัญหานี้
user923487

2

คำตอบก่อนหน้านี้แนะนำให้ใช้[[ $? != 0 ]] && exit $?แต่นี้จะไม่ได้ค่อนข้างทำงานตามที่คาดไว้เพราะ[[ $? != 0 ]]การทดสอบจะรีเซ็ต$?กลับไปที่ศูนย์ซึ่งหมายความว่าแม้ว่าสคริปต์ที่จะออกจากต้นตามที่คาดไว้ก็จะเสมอทางออกด้วยรหัส 0 (ขณะที่ไม่ได้คาดหวัง) . นอกจากนี้ควรใช้-neการทดสอบการเปรียบเทียบเชิงตัวเลขดีกว่า!=การทดสอบการเปรียบเทียบสตริง ดังนั้น IMHO ทางออกที่ดีกว่าคือการใช้:

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

เพื่อให้แน่ใจว่ารหัสการออกจริงถูกตั้งค่าอย่างถูกต้อง


ข้อเสนอแนะที่ดี ฉันแก้ไขรหัสแล้ว
Mike S

1

breakคุณสามารถใช้

จากhelp break:

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

ดังนั้นสำหรับการออกจากลูปสามวงนั่นคือถ้าคุณมีลูปซ้อนกันสองลูปในเมนเฟรมให้ใช้สิ่งนี้เพื่อออกจากพวกมันทั้งหมด:

break 3

มีรหัสมากขึ้นหลังจากลูปดังนั้นการแยกตัวคนเดียวไม่เพียงพอ
user923487

1
ขอบคุณมาก! ตัวอย่างfor((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
กุมภ์ Power

0

exit จะยุติเชลล์ทั้งหมดหรือ sub-shell ปัจจุบัน:

$ 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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.