ท่อ“ รั่ว” ใน linux


12

สมมติว่าคุณมีไพพ์ไลน์ดังนี้:

$ a | b

หากbหยุดการประมวลผล stdin หลังจากนั้นไม่นานท่อจะเต็มและเขียนจากaไปยัง stdout จะบล็อก (จนกว่าจะbเริ่มการประมวลผลอีกครั้งหรือไม่ก็ตาย)

หากฉันต้องการหลีกเลี่ยงสิ่งนี้ฉันอาจถูกล่อลวงให้ใช้ท่อขนาดใหญ่กว่า (หรือมากกว่านั้นbuffer(1)) ได้เช่น:

$ a | buffer | b

นี่จะซื้อเวลาให้ฉันมากขึ้น แต่ในที่สุดaก็จะหยุด

สิ่งที่ฉันชอบ (สำหรับสถานการณ์ที่เฉพาะเจาะจงมากที่ฉันกำลังพูดถึง) คือการมีท่อ "รั่ว" ที่เมื่อเต็มแล้วจะลดลงข้อมูลบางส่วน (นึกคิดทีละบรรทัดโดยบรรทัด) จากบัฟเฟอร์เพื่อให้aดำเนินการต่อ การประมวลผล (อย่างที่คุณคงจินตนาการได้ว่าข้อมูลที่ไหลในไพพ์นั้นมีค่าใช้จ่ายนั่นคือการมีข้อมูลที่ประมวลผลโดยbมีความสำคัญน้อยกว่าการaวิ่งโดยไม่ปิดกั้น)

เพื่อสรุปมันขึ้นมาฉันชอบที่จะมีบางสิ่งบางอย่างเช่นบัฟเฟอร์ที่มีขอบเขตและมีรอยรั่ว:

$ a | leakybuffer | b

ฉันอาจใช้มันได้อย่างง่ายดายในภาษาใด ๆ ฉันแค่สงสัยว่ามีบางสิ่งที่ "พร้อมใช้" (หรือบางสิ่งบางอย่างเช่นทุบตีสายการบินเดียว) ที่ฉันขาดไป

หมายเหตุ: ในตัวอย่างที่ฉันใช้ไพพ์ทั่วไป แต่คำถามก็ใช้ได้กับไพพ์ที่ตั้งชื่อด้วย


ในขณะที่ฉันได้รับคำตอบด้านล่างฉันก็ตัดสินใจที่จะใช้คำสั่ง leakybuffer เพราะวิธีง่ายๆด้านล่างมีข้อ จำกัด บางประการ: https://github.com/CAFxX/leakybuffer


ท่อที่มีชื่อเต็มหรือไม่ ฉันคิดว่าชื่อท่อเป็นวิธีแก้ปัญหานี้ แต่ฉันไม่สามารถพูดได้อย่างแน่นอน
สัญลักษณ์แทน

3
ไปป์ที่มีชื่อ (โดยค่าเริ่มต้น) จะมีความจุเท่ากับท่อที่ไม่มีชื่อ AFAIK
CAFxX

คำตอบ:


14

วิธีที่ง่ายที่สุดคือไปป์ไลน์ผ่านบางโปรแกรมซึ่งตั้งค่าเอาต์พุตที่ไม่บล็อก นี่คือ perl oneliner แบบง่าย ๆ (ซึ่งคุณสามารถบันทึกเป็นแบบรั่วซึม ) ซึ่งทำได้ดังนี้:

ดังนั้นคุณa | bจะกลายเป็น:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

สิ่งที่ทำคืออ่านอินพุตและเขียนไปยังเอาต์พุต (เหมือนกับcat(1)) แต่เอาต์พุตไม่ใช่การบล็อก - หมายความว่าถ้าการเขียนล้มเหลวมันจะส่งคืนข้อผิดพลาดและสูญเสียข้อมูล แต่กระบวนการจะดำเนินการกับบรรทัดถัดไปของอินพุต ความผิดพลาด กระบวนการเป็นชนิดของบัฟเฟอร์บรรทัดตามที่คุณต้องการ แต่ดูข้อแม้ด้านล่าง

คุณสามารถทดสอบด้วยตัวอย่างเช่น:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

คุณจะได้outputไฟล์ที่มีบรรทัดที่หายไป (ผลลัพธ์ที่แน่นอนขึ้นอยู่กับความเร็วของเชลล์ ฯลฯ ) ดังนี้:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

คุณเห็นว่าเปลือกหายไปที่ไหนหลังจาก12773นั้น แต่ก็มีความผิดปกติ - Perl ไม่ได้มีบัฟเฟอร์เพียงพอ12774\nแต่ทำเพื่อ1277ให้มันเขียนแค่นั้น - และหมายเลขถัดไป75610ไม่ได้เริ่มต้นที่จุดเริ่มต้นของบรรทัดทำให้มันน้อย น่าเกลียด

ที่สามารถปรับปรุงได้โดยให้ perl ตรวจสอบเมื่อการเขียนไม่ประสบความสำเร็จอย่างสมบูรณ์และต่อมาพยายามที่จะล้างบรรทัดที่เหลือในขณะที่ละเว้นบรรทัดใหม่เข้ามา แต่ที่จะซับซ้อนสคริปต์ Perl มากขึ้นจึงเป็นแบบฝึกหัดสำหรับ ผู้อ่านที่สนใจ :)

อัปเดต (สำหรับไฟล์ไบนารี): หากคุณไม่ได้ประมวลผลบรรทัดที่ยกเลิกบรรทัดใหม่ (เช่นล็อกไฟล์หรือที่คล้ายกัน) คุณจะต้องเปลี่ยนคำสั่งเล็กน้อยมิฉะนั้น Perl จะใช้หน่วยความจำจำนวนมาก (ขึ้นอยู่กับจำนวนบรรทัดอักขระที่ปรากฏในอินพุต):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

มันจะทำงานอย่างถูกต้องสำหรับไฟล์ไบนารีด้วย (โดยไม่ต้องใช้หน่วยความจำเพิ่มเติม)

Update2 - เอาต์พุตไฟล์ข้อความที่ดีกว่า: หลีกเลี่ยงบัฟเฟอร์เอาต์พุต ( syswriteแทนprint):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

ดูเหมือนว่าจะแก้ไขปัญหากับ "เส้นที่ผสาน" สำหรับฉัน:

12766
12767
12768
16384
16385
16386

(หมายเหตุ: หนึ่งสามารถตรวจสอบว่าการส่งออกบรรทัดถูกตัดด้วย: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)


ฉันชอบ
ออนไลเนอร์

1
ดูเหมือนว่าจะทำงานได้ในระดับหนึ่ง แต่เมื่อฉันดูคำสั่งของฉันซึ่งก็คือperl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000Perl ดูเหมือนจะจัดสรรหน่วยความจำอย่างต่อเนื่องจนกว่าผู้จัดการ OOM จะถูกฆ่า
Ponkadoodle

@ Walacoloo ขอบคุณที่ชี้ให้เห็นว่ากรณีของฉันคือการสตรีมไฟล์บันทึก ... ดูคำตอบที่ปรับปรุงสำหรับการเปลี่ยนแปลงเล็กน้อยที่จำเป็นในการสนับสนุนไฟล์ไบนารี
Matija Nalis

ดูเพิ่มเติม GNU 'sdd dd oflag=nonblock status=none
Stéphane Chazelas

1
ขออภัยที่ไม่ดีของฉันอีกครั้งจริง ๆ แล้วเขียนน้อยกว่า PIPE_BUF ไบต์ (4096 บน Linux ต้องมีอย่างน้อย 512 โดย POSIX) มีการรับประกันว่าเป็นอะตอมดังนั้นวิธีการ$| = 1ของคุณsyswrite()ป้องกันการเขียนสั้น ๆ อย่างแน่นอนตราบใดที่บรรทัดสั้นพอสมควร
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.