เหตุใดจึงไม่ทุบตีในขณะที่ออกจากวงเมื่อท่อไปที่คำสั่งย่อยที่ถูกยกเลิก?


12

ทำไมคำสั่งด้านล่างจึงไม่ออก? แทนที่จะออกจากการวนซ้ำจะวิ่งไปเรื่อย ๆ

ในขณะที่ฉันค้นพบพฤติกรรมนี้โดยใช้การตั้งค่าที่ซับซ้อนมากขึ้นรูปแบบที่ง่ายที่สุดของคำสั่งจะลดลงไปดังต่อไปนี้

ไม่ออก:

while /usr/bin/true ; do echo "ok" | cat ; done | exit 1

ไม่มีการพิมพ์ผิดด้านบน แต่ละ '|' เป็นท่อ 'ทางออก 1' ย่อมาจากกระบวนการอื่นที่วิ่งและออก

ฉันคาดว่า "exit 1" จะทำให้เกิด SIGPIPE ในขณะที่ลูป (เขียนบนไพพ์ที่ไม่มีตัวอ่าน) และเพื่อให้ลูปแตกออก แต่การวนซ้ำยังคงทำงานอยู่

เหตุใดคำสั่งจึงไม่หยุด


zsh ออกตามปกติ
Braiam

คำตอบ:


13

มันเป็นเพราะทางเลือกในการใช้งาน

การรันสคริปต์เดียวกันบน Solaris ด้วยการksh93สร้างพฤติกรรมที่แตกต่าง:

$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]

สิ่งที่ทำให้เกิดปัญหาคือไปป์ไลน์ด้านในโดยที่ไม่มีปัญหาลูปจะออกจากเชลล์ / OS:

$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$

cat กำลังรับสัญญาณ SIGPIPE ภายใต้ bash แต่ shell กำลังวนลูปอยู่ดี

Process 5659 suspended
[pid 28801] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28801] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28801 detached
Process 28800 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28802 attached
Process 28803 attached
[pid 28803] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
Process 5659 suspended
[pid 28803] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28803 detached
Process 28802 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28804 attached
Process 28805 attached (waiting for parent)
Process 28805 resumed (parent 5659 ready)
Process 5659 suspended
[pid 28805] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28805] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28805 detached
Process 28804 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

สถานะเอกสารBash :

เชลล์รอคำสั่งทั้งหมดในไปป์ไลน์เพื่อยกเลิกก่อนที่จะส่งคืนค่า

สถานะเอกสารKsh :

แต่ละคำสั่งยกเว้นอาจเป็นครั้งสุดท้ายถูกเรียกใช้เป็นกระบวนการแยกต่างหาก เชลล์รอให้คำสั่งสุดท้ายยุติ

สถานะPOSIX :

หากไพพ์ไลน์ไม่อยู่ในพื้นหลัง (ดูรายการแบบอะซิงโครนัส), เชลล์จะรอให้คำสั่งสุดท้ายที่ระบุในไพพ์ไลน์เสร็จสมบูรณ์และอาจรอให้คำสั่งทั้งหมดดำเนินการให้เสร็จสิ้น


ฉันคิดว่ามันไม่ได้เป็นไปป์ไลน์ภายในที่ก่อให้เกิดปัญหามันเป็นที่ builtin echoละเว้น SIGPIPE นอกจากนี้คุณยังสามารถสร้างปัญหาขึ้นอีกครั้งโดยใช้env echoแทนecho(เพื่อบังคับให้echoไบนารีจริงใช้) (เปรียบเทียบกับผลลัพธ์ของ{ echo hi; echo $? >&2; } | exit 1และ{ env echo hi; echo $? >&2; } | exit 1.)
Lucas Werkmeister

1

ปัญหานี้ทำให้ฉันล้มเหลวมานานหลายปี ขอบคุณ jilliagre สำหรับเขยิบในทิศทางที่ถูกต้อง

การตั้งคำถามเล็ก ๆ น้อย ๆ บนกล่อง linux ของฉันสิ่งนี้ก็จบลงอย่างที่คาดไว้:

while true ; do echo "ok"; done | head

แต่ถ้าฉันเพิ่มไปป์มันจะไม่ออกอย่างที่คาดไว้:

while true ; do echo "ok" | cat; done | head

นั่นทำให้ฉันผิดหวังมาหลายปี เมื่อพิจารณาคำตอบที่เขียนโดย jilliagre ฉันคิดวิธีแก้ปัญหาที่ยอดเยี่ยมนี้ขึ้นมา:

while true ; do echo "ok" | cat || exit; done | head

ถาม ...

ก็ไม่มาก นี่คือบางสิ่งที่ซับซ้อนกว่านี้เล็กน้อย:

i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' || exit
done | head

มันใช้งานไม่ได้ ฉันเพิ่ม|| exitดังนั้นมันจึงรู้วิธีที่จะยุติก่อน แต่คนแรกechoไม่ตรงกับgrepดังนั้นวงออกจากทันที grepในกรณีนี้คุณจะไม่ได้รับความสนใจในการออกจากสถานะของ catฉันทำงานรอบคือการเพิ่มอีก ดังนั้นนี่คือสคริปต์ที่ประดิษฐ์ขึ้นที่เรียกว่า "หมื่น":

#!/bin/bash
i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' | cat || exit
done

tens | headนี้ต้องสิ้นสุดลงเมื่อใช้เป็น ขอบคุณพระเจ้า.

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