grep ไม่ส่งออกจนกว่า EOF หากส่งผ่าน cat


19

รับตัวอย่างที่น้อยที่สุดนี้

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

มันจะออกผลลัพธ์LINE 1และแล้วหลังจากที่สองเอาท์พุทLINE 2, คาดว่าเป็น


ถ้าเราไปป์นี้ให้ได้ grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

พฤติกรรมจะเหมือนกับในกรณีก่อนหน้าตามที่คาดไว้


หากอีกวิธีหนึ่งเราจะทำสิ่งนี้เพื่อ cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

พฤติกรรมจะเหมือนเดิมตามที่คาดไว้


แต่ถ้าท่อเราไปgrep LINEแล้วไปcat,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

มีการส่งออกจนกว่าจะมีใครผ่านไปสองไม่มีและทั้งสองเส้นปรากฏในการส่งออกทันทีซึ่งผมไม่ได้คาดหวัง


เหตุใดสิ่งนี้จึงเกิดขึ้นและฉันจะทำให้รุ่นสุดท้ายทำงานในลักษณะเดียวกันกับคำสั่งสามคำแรกได้อย่างไร


catต่อไฟล์เข้าด้วยกัน คุณกำลังพยายามทำอะไรด้วยการส่งไปยังcat?
Douglas Held

15
@DouglasHeld เมื่อเรียกโดยไม่มีข้อโต้แย้งcatเพียงแค่อ่านและเอาท์พุทเข้าไปstdin stdoutแน่นอนฉันมากับคำถามนี้ด้วยสิ่งที่ซับซ้อนจำนวนมากแทนechoและcatแต่สิ่งเหล่านี้กลับกลายเป็นสิ่งที่ไม่เกี่ยวข้องเนื่องจากปัญหาแสดงขึ้นด้วยตัวอย่างที่ง่ายกว่ามาก
lisyarus

3
@DouglasHeld: ท่อไปที่แมวมักจะมีประโยชน์ในการบังคับให้ stdout ไม่ใช่เทอร์มินัล ตัวอย่างเช่นนี่เป็นวิธีที่ง่ายในการรับหลายคำสั่งเพื่อไม่ใช้เอาต์พุต colorized
wchargin

ฉันสาบานได้เลยว่านี่เป็นคำถามซ้ำใน Stack Overflow!
iBug

@wchargin ขอบคุณมากคุณได้สอนสิ่งใหม่เกี่ยวกับ posix ที่ฉันไม่เคยรู้มาก่อน
Douglas Held

คำตอบ:


38

เมื่อ (อย่างน้อย GNU) grepเอาต์พุตไม่ใช่เทอร์มินัลจะบัฟเฟอร์เอาต์พุตของมันซึ่งเป็นสิ่งที่ทำให้เกิดพฤติกรรมที่คุณเห็น คุณสามารถปิดใช้งานสิ่งนี้ได้โดยใช้ตัวเลือกgrepของGNU --line-buffered:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

หรือstdbufยูทิลิตี้:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

ปิดการบัฟเฟอร์ในไปป์มีมากขึ้นในหัวข้อนี้


26

คำอธิบายที่ง่าย

เช่นเดียวกับสาธารณูปโภคจำนวนมากนี้ไม่ได้เป็นสิ่งที่แปลกประหลาดในการเขียนโปรแกรมหนึ่งgrepที่แตกต่างกันออกมาตรฐานระหว่างถูกสายบัฟเฟอร์และบัฟเฟอร์อย่างเต็มที่ ในกรณีก่อนหน้านี้ C ไลบรารีบัฟเฟอร์ส่งออกข้อมูลในหน่วยความจำจนกระทั่งบัฟเฟอร์ที่เก็บข้อมูลเหล่านั้นถูกเติมหรืออักขระ linefeed ถูกเพิ่มเข้าไป (หรือโปรแกรมสิ้นสุดอย่างหมดจด) โดยที่มันเรียกใช้write()เพื่อเขียนเนื้อหาบัฟเฟอร์จริง ในกรณีหลังเฉพาะในหน่วยความจำบัฟเฟอร์กลายเป็นเต็มรูปแบบ (หรือโปรแกรมสิ้นสุดหมดจด) write()ทริกเกอร์

คำอธิบายโดยละเอียดเพิ่มเติม

นี่เป็นคำอธิบายที่รู้จักกันดี แต่ผิดเล็กน้อย ในความเป็นจริงเอาต์พุตมาตรฐานไม่ได้ถูกจัดเรียงบรรทัด แต่บัฟเฟอร์อัจฉริยะในไลบรารี GNU C และไลบรารี BSD C ออกมาตรฐานเป็นยังล้างเมื่ออ่านมาตรฐานการป้อนข้อมูล exhausts ของบัฟเฟอร์ในหน่วยความจำ (จากการป้อนข้อมูลก่อนอ่าน) และห้องสมุด C ที่มีการเรียกread()ดึงข้อมูลการป้อนข้อมูลเพิ่มเติมบางอย่างและมันก็เป็นจุดเริ่มต้นการอ่านของสายใหม่ (เหตุผลหนึ่งคือเพื่อป้องกันการหยุดชะงักเมื่อโปรแกรมอื่นเชื่อมต่อตัวเองกับปลายทั้งสองของตัวกรองและคาดว่าจะสามารถใช้งานแบบทีละบรรทัดสลับกันระหว่างการเขียนตัวกรองและการอ่านจากมันเช่น "ตัวประมวลผลร่วม" ใน GNU awkตัวอย่างเช่น.)

อิทธิพลของไลบรารี C

grepและยูทิลิตี้อื่นทำสิ่งนี้ - หรือที่เข้มงวดกว่าคือไลบรารี C ที่พวกเขาใช้ทำสิ่งนี้เพราะนี่เป็นคุณสมบัติที่กำหนดไว้ของการเขียนโปรแกรมในภาษา C - ตามสิ่งที่ตรวจพบเอาท์พุทมาตรฐานของพวกเขา หาก (และเฉพาะในกรณีที่) ไม่ใช่อุปกรณ์เชิงโต้ตอบพวกเขาเลือกการบัฟเฟอร์เต็มมิฉะนั้นพวกเขาเลือกการบัฟเฟอร์อัจฉริยะ ไพพ์ถือเป็นอุปกรณ์แบบไม่โต้ตอบเนื่องจากคำจำกัดความของการเป็นอุปกรณ์แบบอินเทอร์แอคทีฟอย่างน้อยที่สุดในโลกของ Unix และ Linux นั้นการisatty()โทรกลับเป็นจริงสำหรับไฟล์ descriptor ที่เกี่ยวข้อง

แก้ไขปัญหาเพื่อปิดใช้งานการบัฟเฟอร์แบบเต็ม

ยูทิลิตี้บางอย่างเช่นgrepมีตัวเลือกที่เป็นนิสัยเช่น--line-bufferedนั้นเปลี่ยนการตัดสินใจนี้ซึ่งคุณสามารถดูได้ว่าตั้งชื่อผิด แต่ส่วนน้อยของโปรแกรมตัวกรองที่สามารถใช้จริงมีตัวเลือกดังกล่าว

โดยทั่วไปแล้วเราสามารถใช้เครื่องมือที่ขุดเข้าไปใน internals เฉพาะของ C library และเปลี่ยนการตัดสินใจ (ซึ่งมีปัญหาด้านความปลอดภัยหากโปรแกรมที่จะแก้ไขนั้นถูกตั้งค่าเป็น UID และยังเฉพาะเจาะจงกับ C C และแน่นอน โดยเฉพาะกับโปรแกรมที่เขียนหรือเลเยอร์ด้านบนของภาษา C) หรือเครื่องมือต่าง ๆ เช่นptybandageที่ไม่ได้เปลี่ยน internals ของโปรแกรม แต่เพียงแค่แทรกเทอร์มินัลหลอกหลอกเป็นเทอร์มินัลมาตรฐานเพื่อให้การตัดสินใจออกมาว่า ส่งผลกระทบต่อสิ่งนี้

อ่านเพิ่มเติม


1
หากวลี "บรรทัดบัฟเฟอร์" คือการเรียกชื่อผิดแล้วมันไม่ได้จริงๆความผิดของgrepแต่สายห้องสมุดพื้นฐาน/setbuf setvbufฉันไม่รู้การอ้างอิงออนไลน์ที่เชื่อถือได้สำหรับมาตรฐาน C แต่เช่นหน้าลินุกซ์และ FreeBSD พร้อมกับคำอธิบาย POSIX setvbufเรียกมันว่า "line buffered" _IOLBFแม้สัญลักษณ์คงเพราะมันเป็น
ilkkachu

ตอนนี้คุณเรียนรู้ได้ดีขึ้นแล้ว กลยุทธ์การกำหนดบัฟเฟอร์นี้มีการอธิบายไว้ในเอกสารของไลบรารี GNU C แม้ว่าจะเป็นช่วงสั้น ๆ Laurent Bercot เป็นคนตรงไปตรงมามากกว่าในเรื่องนี้ ฉันได้พูดถึงมันเช่นกัน
JdeBP

ฉันไม่คิดว่า "ความคาดหวังของคุณผิด" เป็นหัวข้อที่ดีสำหรับคำอธิบายที่ยอดเยี่ยมของการบัฟเฟอร์ผลลัพธ์ ฉันหวังว่าคุณจะไม่รังเกียจที่จะลบมันออกและเพิ่มหัวเรื่องที่เป็นคำอธิบายสำหรับแต่ละส่วนของคำตอบ
Anthony G - ความยุติธรรมสำหรับโมนิก้า

2
@ilkkachu มาตรฐาน C ใช้ "line buffered" แน่นอน ไฟล์ 7.21.3ต่อย่อหน้า 3 : "เมื่อกระแสข้อมูลไม่ถูกบัฟเฟอร์ ... เมื่อกระแสข้อมูลถูกบัฟเฟอร์เต็ม ... เมื่อกระแสข้อมูลถูกบัฟเฟอร์บรรทัดตัวอักษรมีวัตถุประสงค์เพื่อส่งไปยังหรือจากสภาพแวดล้อมโฮสต์เป็น บล็อกเมื่อพบอักขระบรรทัดใหม่ ... "อันที่จริง C Standard ใช้วลีที่ถูกต้อง" line buffered "ห้าครั้ง ดังนั้นจึงไม่ใช่การเรียกชื่อผิด
Andrew Henle

1
ยิ่งไปกว่านั้นวิธีที่อธิบายไว้ในที่นี้คือ "การบัฟเฟอร์อัจฉริยะ" ตามที่ฉันเข้าใจแล้วดูเหมือนจะเป็นสิ่งที่มาตรฐาน C อธิบายว่าเป็น "การบัฟเฟอร์บรรทัด" โดยเฉพาะอย่างยิ่งนอกเหนือจากการล้างบัฟเฟอร์ที่บรรทัดใหม่ "เมื่อกระแสข้อมูลถูกบัฟเฟอร์บรรทัดตัวอักษรมีวัตถุประสงค์เพื่อส่งไปยังหรือจากสภาพแวดล้อมโฮสต์เป็นบล็อกเมื่อมีการร้องขออินพุท [... ] บนสตรีมที่ไม่มีบัฟเฟอร์หรือเมื่อ มีการร้องขออินพุตในสตรีมบัฟเฟอร์บรรทัดที่ต้องการการส่งอักขระจากสภาพแวดล้อมโฮสต์ " ดังนั้นนี่ไม่ใช่การเล่นโวหาร GNU หรือ BSD แต่เป็นสิ่งที่ภาษาต้องการ
John Bollinger

7

ใช้

grep --line-buffered

เพื่อให้ grep ไม่บัฟเฟอร์มากกว่าหนึ่งบรรทัดในแต่ละครั้ง

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