ท่อการไหลของข้อมูลในไปป์ไลน์เป็นอย่างไร


22

ฉันไม่เข้าใจว่าข้อมูลไหลในท่อและหวังว่าใครบางคนสามารถชี้แจงสิ่งที่เกิดขึ้นที่นั่น

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

แต่ดูเหมือนว่ามันจะไม่เป็นเช่นนั้น

นี่คือตัวอย่างการทดสอบ มีข้อความบางบรรทัด ฉันพิมพ์ใหญ่และทำซ้ำแต่ละบรรทัดสองครั้ง cat text | tr '[:lower:]' '[:upper:]' | sed 'p'ผมทำด้วย

ที่จะปฏิบัติตามกระบวนการที่เราสามารถเรียกมันว่า "การโต้ตอบ" - catข้ามชื่อไฟล์การป้อนข้อมูลใน แต่ละส่วนของไปป์ไลน์ทำงานทีละบรรทัด:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

แต่ไปป์ไลน์ที่สมบูรณ์รอให้ฉันเสร็จสิ้นอินพุตด้วยEOFและจากนั้นพิมพ์ผลลัพธ์:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

มันควรจะเป็นอย่างนั้นเหรอ? ทำไมมันไม่ได้ทีละบรรทัด


มันไม่ใช่ท่อ แต่ก็catบัฟเฟอร์จนกว่า stdin จะปิด
goldilocks

แต่trและsedทำกระบวนการต่อจากcatก่อนที่ stdin จะปิด
xealits

ค่าเริ่มต้นที่ใช้โดย stdio (ซึ่งฉันเชื่อว่าการใช้งานของโปรแกรมที่กล่าวถึงทั้งหมด) คือ stderr นั้นไม่มีข้อผิดพลาดและ stdout เป็น line buffered เมื่อเขียนไปยังเทอร์มินัลและบัฟเฟอร์อย่างสมบูรณ์ (ตัวอย่างเช่น . คำสั่งบางคำสั่งมีแฟล็กที่สามารถเปลี่ยนการบัฟเฟอร์ stdout แต่ดูเหมือนว่า tr ไม่มี
kasperd

คำตอบ:


36

มีกฎการบัฟเฟอร์ทั่วไปตามด้วยไลบรารี I / O มาตรฐาน C ( stdio) ที่โปรแกรมยูนิกซ์ส่วนใหญ่ใช้ หากเอาต์พุตกำลังส่งไปยังเทอร์มินัลจะถูกล้างข้อมูลที่ท้ายของแต่ละบรรทัด มิฉะนั้นจะถูกล้างข้อมูลเมื่อบัฟเฟอร์ (8K บนระบบ Linux / amd64 ของฉัน; อาจแตกต่างกับของคุณ) เต็ม

ถ้าสาธารณูปโภคทั้งหมดของคุณกำลังตามกฎทั่วไปคุณจะเห็นผลลัพธ์ล่าช้าในทุกตัวอย่างของคุณ ( cat|sed, cat|trและcat|tr|sed) แต่มีข้อยกเว้น: GNU catไม่เคยบัฟเฟอร์ผลลัพธ์ของมัน ไม่สามารถใช้stdioหรือเปลี่ยนstdioนโยบายการบัฟเฟอร์เริ่มต้น

ฉันค่อนข้างแน่ใจว่าคุณกำลังใช้ GNU catและไม่ใช่ยูนิกซ์อื่น ๆcatเพราะคนอื่นจะไม่ทำแบบนี้ Unix ดั้งเดิมcatมี-uตัวเลือกในการร้องขอเอาต์พุตที่ไม่มีบัฟเฟอร์ GNU catเพิกเฉย-uตัวเลือกนี้เนื่องจากเอาต์พุตของมันจะไม่บัฟเฟอร์เสมอ

ดังนั้นเมื่อใดก็ตามที่คุณมีไพพ์catทางด้านซ้ายในระบบ GNU การส่งผ่านข้อมูลทางไพพ์จะไม่ล่าช้า catจะไม่ได้ไปทีละบรรทัด - terminal ของคุณจะทำอย่างนั้น ในขณะที่คุณป้อนพิมพ์กำลังสำหรับแมว terminal ของคุณอยู่ในโหมด "บัญญัติ" - สายตามที่มีการแก้ไขปุ่ม Backspace เหมือนและ Ctrl-U Enterให้คุณมีโอกาสที่จะแก้ไขบรรทัดที่คุณพิมพ์ก่อนที่จะส่งมันด้วย

ในcat|tr|sedตัวอย่างtrยังคงรับข้อมูลจากcatทันทีที่คุณกดEnterแต่trทำตามstdioนโยบายเริ่มต้น: เอาต์พุตจะไปที่ไพพ์ดังนั้นจึงไม่ล้างข้อมูลหลังจากแต่ละบรรทัด มันเขียนไปยังไปป์ที่สองเมื่อบัฟเฟอร์เต็มหรือเมื่อได้รับ EOF แล้วแต่ว่าจะถึงอย่างใดก่อน

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

GNU sedมี-uตัวเลือกดังนั้นหากคุณกลับคำสั่งซื้อและใช้cat|sed -u|trคุณจะเห็นผลลัพธ์ปรากฏขึ้นอีกครั้งทันที (ในsed -uตัวเลือกอื่น ๆ อาจจะมี แต่ผมไม่คิดว่ามันเป็นประเพณีโบราณเช่นยูนิกซ์cat -u) trเท่าที่ผมสามารถบอกได้ว่าไม่มีตัวเลือกที่เทียบเท่า

มียูทิลิตีที่เรียกว่าstdbufซึ่งให้คุณเปลี่ยนโหมดบัฟเฟอร์ของคำสั่งใด ๆ ที่ใช้stdioค่าเริ่มต้น มันค่อนข้างบอบบางเนื่องจากใช้LD_PRELOADเพื่อทำสิ่งที่ห้องสมุด C ไม่ได้ออกแบบมาเพื่อรองรับ แต่ในกรณีนี้ดูเหมือนว่าจะทำงาน:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

1
ขอบคุณ! คำตอบที่ยอดเยี่ยม อาจเป็นไปได้ว่าฉันควรพูดถึงบัฟเฟอร์ในบางวิธีเพื่อให้สามารถค้นหาได้
xealits

teeและddมักจะเล่นตามกฎของตัวเอง เมื่อรวมกันอย่างจินตนาการเครื่องมือทั้งสามนี้สามารถลบล้างความต้องการใด ๆstdbufในท่อที่มีพื้นหลัง
mikeserv

1
นี้เป็นหนึ่งในเหตุผลที่จะหลีกเลี่ยงการใช้งานที่ไร้ประโยชน์ของแมว
ฮอบส์

8

ที่จริงแล้วฉันคิดว่าต้องเข้าใจและตอบมากกว่านี้ คำถามที่ดี (ฉันจะโหวตมันต่อไป)

คุณเพิกเฉยที่จะลองtr | sedแก้ไขข้อบกพร่องด้านบน:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

trบัฟเฟอร์อย่างเห็นได้ชัด เรียนรู้สิ่งใหม่ทุกวัน!

แก้ไข :

อย่างที่ฉันคิดไว้เราได้แยกสาเหตุ แต่ไม่ได้อธิบาย หากคุณcat | trก็เขียนได้ทันทีถ้าคุณcat | sedก็เขียนได้ทันที แต่ถ้าคุณtr | sedก็รอEOFสำหรับ ฉันอยากจะแนะนำคำตอบอาจถูกฝังอยู่ในtrหรือsedซอร์สโค้ดแล้วและไม่เป็นปัญหาไปป์

แก้ไข :

ฉันเห็น Wumpus ให้คำอธิบายขณะที่ฉันพิมพ์การแก้ไขล่าสุด ขอบคุณ!


1
แน่นอนพวกเขาบัฟเฟอร์! และการทดสอบที่มีเส้นขนาด 8kb ตามที่ Wumpus กล่าวไว้แสดงว่าบัฟเฟอร์นั้นมีขนาด 8Kb ฉันต้องการยอมรับคำตอบทั้งสองเพื่อแบ่งปันชื่อเสียงบางส่วน แต่ฉันจะทำให้ Wumpus เป็นคำตอบที่สมบูรณ์ยิ่งขึ้น ขอขอบคุณ!
xealits

1
ไม่มีปัญหาฉันเป็นคำตอบเชิงประจักษ์เขาเป็นคนที่มีความรู้
Poisson Aerohead

ดูคำถามนี้ซึ่งแสดงวิธีการใช้stdbufซึ่งอาจมีประโยชน์ด้วย unix.stackexchange.com/questions/182537/…
Joe
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.