พวกเขาสอดแทรก! คุณลองใช้เอาท์พุตสั้น ๆ ซึ่งยังคง unsplit แต่ในทางปฏิบัติมันยากที่จะรับประกันได้ว่าเอาต์พุตใด ๆ โดยเฉพาะจะยังคง unsplit
บัฟเฟอร์ออก
ขึ้นอยู่กับวิธีที่โปรแกรมบัฟเฟอร์เอาต์พุต ห้องสมุด stdioว่าโปรแกรมส่วนใหญ่ใช้เมื่อพวกเขากำลังเขียนใช้บัฟเฟอร์ที่จะทำให้การส่งออกมีประสิทธิภาพมากขึ้น แทนที่จะส่งออกข้อมูลทันทีที่โปรแกรมเรียกใช้ฟังก์ชันไลบรารีเพื่อเขียนไปยังไฟล์ฟังก์ชันจะเก็บข้อมูลนี้ไว้ในบัฟเฟอร์และจะส่งออกข้อมูลเมื่อบัฟเฟอร์เต็มแล้วเท่านั้น ซึ่งหมายความว่าเอาต์พุตจะถูกทำเป็นแบตช์ แม่นยำยิ่งขึ้นมีโหมดเอาต์พุตสามโหมด:
- Unbuffered: ข้อมูลถูกเขียนทันทีโดยไม่ต้องใช้บัฟเฟอร์ สิ่งนี้อาจช้าหากโปรแกรมเขียนเอาต์พุตเป็นชิ้นเล็ก ๆ เช่นตัวอักษรต่ออักขระ นี่เป็นโหมดเริ่มต้นสำหรับข้อผิดพลาดมาตรฐาน
- บัฟเฟอร์เต็ม: ข้อมูลจะถูกเขียนเมื่อบัฟเฟอร์เต็มเท่านั้น นี่เป็นโหมดเริ่มต้นเมื่อเขียนไปที่ไพพ์หรือไฟล์ปกติยกเว้นด้วย stderr
- Line-buffered: ข้อมูลถูกเขียนหลังแต่ละบรรทัดใหม่หรือเมื่อบัฟเฟอร์เต็ม นี่เป็นโหมดเริ่มต้นเมื่อเขียนไปยังเทอร์มินัลยกเว้น stderr
โปรแกรมสามารถโปรแกรมใหม่แต่ละไฟล์ให้ทำงานแตกต่างกันและสามารถล้างบัฟเฟอร์อย่างชัดเจน บัฟเฟอร์จะถูกล้างโดยอัตโนมัติเมื่อโปรแกรมปิดไฟล์หรือออกจากโปรแกรมตามปกติ
หากโปรแกรมทั้งหมดที่กำลังเขียนไปยังไปป์เดียวกันสามารถใช้โหมด line-buffered หรือใช้โหมด unbuffered และเขียนแต่ละบรรทัดด้วยการเรียกครั้งเดียวไปยังฟังก์ชั่นเอาต์พุตและหากบรรทัดสั้นพอที่จะเขียนในกลุ่มเดียวแล้ว การส่งออกจะเป็น interleaving ของเส้นทั้งหมด แต่ถ้าหนึ่งในโปรแกรมใช้โหมดเต็มบัฟเฟอร์หรือหากบรรทัดยาวเกินไปคุณจะเห็นเส้นผสม
นี่คือตัวอย่างที่ฉัน interleave ผลลัพธ์จากสองโปรแกรม ฉันใช้ GNU coreutils บน Linux ยูทิลิตี้รุ่นต่าง ๆ เหล่านี้อาจทำงานต่างกัน
yes aaaa
เขียนaaaa
ตลอดไปในสิ่งที่เทียบเท่ากับโหมด line-buffered yes
ยูทิลิตี้จริงเขียนหลายบรรทัดในเวลา แต่ทุกครั้งที่มันปล่อยออกมาเอาท์พุทเอาท์พุทเป็นจำนวนทั้งหมดของเส้น
echo bbbb; done | grep b
เขียนbbbb
ตลอดไปในโหมดเต็มบัฟเฟอร์ มันใช้ขนาดบัฟเฟอร์ 8192 และแต่ละบรรทัดมีความยาว 5 ไบต์ ตั้งแต่ 5 ไม่ได้แบ่ง 8192 ขอบเขตระหว่างการเขียนไม่ได้อยู่ที่ขอบเขตของเส้นโดยทั่วไป
ลองผสมมันเข้าด้วยกัน
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
อย่างที่คุณเห็นใช่บางครั้งขัดจังหวะ grep และในทางกลับกัน มีเพียงประมาณ 0.001% ของบรรทัดที่ถูกขัดจังหวะ แต่มันเกิดขึ้น ผลลัพธ์จะถูกสุ่มดังนั้นจำนวนการขัดจังหวะจะแตกต่างกัน แต่ฉันเห็นการขัดจังหวะอย่างน้อยสองสามครั้งทุกครั้ง จะมีส่วนของเส้นที่ถูกขัดจังหวะมากกว่าถ้าเส้นนั้นยาวกว่าเนื่องจากความน่าจะเป็นของการขัดจังหวะจะเพิ่มขึ้นเมื่อจำนวนบรรทัดต่อบัฟเฟอร์ลดลง
มีหลายวิธีที่จะมีการปรับบัฟเฟอร์เอาท์พุท คนหลักคือ:
- ปิดการบัฟเฟอร์ในโปรแกรมที่ใช้ไลบรารี stdio โดยไม่เปลี่ยนการตั้งค่าเริ่มต้นด้วยโปรแกรมที่
stdbuf -o0
พบใน GNU coreutils และระบบอื่น ๆ เช่น FreeBSD stdbuf -oL
หรือคุณสามารถสลับไปยังเส้นบัฟเฟอร์ด้วย
- สลับไปที่การบัฟเฟอร์บรรทัดโดยสั่งเอาต์พุตของโปรแกรมผ่านเทอร์มินัลที่สร้างขึ้นเพื่อจุดประสงค์นี้
unbuffer
เท่านั้น บางโปรแกรมอาจทำงานแตกต่างกันในวิธีอื่นตัวอย่างเช่นgrep
ใช้สีตามค่าเริ่มต้นหากเอาต์พุตเป็นเทอร์มินัล
- กำหนดค่าโปรแกรมตัวอย่างเช่นส่งผ่าน
--line-buffered
ไปยัง GNU grep
ลองดูตัวอย่างด้านบนอีกครั้งคราวนี้มีการบัฟเฟอร์บรรทัดทั้งสองด้าน
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
ดังนั้นครั้งนี้ใช่ไม่เคยขัดจังหวะ grep แต่บางครั้ง grep ขัดจังหวะใช่ ฉันจะมาทำไมในภายหลัง
การแทรกสอดของท่อ
ตราบใดที่แต่ละโปรแกรมเอาต์พุตทีละหนึ่งบรรทัดและบรรทัดนั้นสั้นพอบรรทัดเอาต์พุตจะถูกแยกอย่างเรียบร้อย แต่มีข้อ จำกัด ว่าจะใช้งานได้นานแค่ไหน ไปป์เองมีบัฟเฟอร์การโอนย้าย เมื่อโปรแกรมส่งออกไปยังไพพ์ข้อมูลจะถูกคัดลอกจากโปรแกรมตัวเขียนไปยังบัฟเฟอร์การโอนของไพพ์และจากนั้นภายหลังจากบัฟเฟอร์การโอนของไพพ์ไปยังโปรแกรมอ่าน (อย่างน้อยในแนวความคิด - บางครั้งเคอร์เนลอาจปรับให้เหมาะกับสำเนาเดียว)
หากมีข้อมูลที่จะคัดลอกมากกว่าที่พอดีในบัฟเฟอร์การโอนของไพพ์เคอร์เนลจะคัดลอกหนึ่งบัฟเฟอร์ในแต่ละครั้ง หากมีหลายโปรแกรมกำลังเขียนไปยังไพพ์เดียวกันและโปรแกรมแรกที่เคอร์เนลเลือกต้องการเขียนมากกว่าหนึ่งบัฟเฟอร์จะไม่มีการรับประกันว่าเคอร์เนลจะเลือกโปรแกรมเดียวกันอีกครั้งในครั้งที่สอง ตัวอย่างเช่นถ้าPคือขนาดบัฟเฟอร์foo
ต้องการที่จะเขียน 2 * Pไบต์และbar
ต้องการที่จะเขียน 3 ไบต์แล้วหนึ่ง interleaving เป็นไปได้คือPไบต์จากfoo
นั้น 3 ไบต์จากbar
และPfoo
ไบต์จาก
กลับมาที่ตัวอย่าง yes + grep ด้านบนในระบบของฉันyes aaaa
เกิดขึ้นกับการเขียนหลายบรรทัดที่สามารถใส่ในบัฟเฟอร์ 8192- ไบต์ในคราวเดียว เนื่องจากมี 5 ไบต์ในการเขียน (4 อักขระที่พิมพ์ได้และขึ้นบรรทัดใหม่) นั่นหมายถึงมันเขียน 8190 ไบต์ทุกครั้ง ขนาดบัฟเฟอร์ของไพพ์คือ 4096 ไบต์ ดังนั้นจึงเป็นไปได้ที่จะได้รับ 4096 ไบต์จากใช่จากนั้นออกบางส่วนจาก grep และจากนั้นส่วนที่เหลือของการเขียนจากใช่ (8190 - 4096 = 4094 ไบต์) 4096 ไบต์ห้องใบสำหรับ 819 เส้นที่มีและคนเดียวaaaa
a
ดังนั้นสอดคล้องกับคนเดียวนี้a
ตามด้วยหนึ่งเขียนจาก grep abbbb
ให้สอดคล้องกับ
หากคุณต้องการดูรายละเอียดของสิ่งที่เกิดขึ้นแล้วgetconf PIPE_BUF .
จะบอกขนาดของบัฟเฟอร์ท่อในระบบของคุณและคุณสามารถดูรายการการเรียกระบบทั้งหมดที่ทำโดยแต่ละโปรแกรมด้วย
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
วิธีการรับประกัน interleaving เส้นสะอาด
หากความยาวบรรทัดมีขนาดเล็กกว่าขนาดบัฟเฟอร์ของท่อการบัฟเฟอร์บรรทัดรับประกันว่าจะไม่มีบรรทัดผสมในเอาต์พุต
หากความยาวบรรทัดสามารถมากกว่านั้นได้จะไม่มีทางหลีกเลี่ยงการมั่วโดยพลการเมื่อโปรแกรมหลายโปรแกรมกำลังเขียนไปยังไปป์เดียวกัน เพื่อให้แน่ใจว่ามีการแยกคุณต้องทำให้แต่ละโปรแกรมเขียนไปยังไพพ์ที่แตกต่างกันและใช้โปรแกรมเพื่อรวมบรรทัด ตัวอย่างเช่นGNU Parallelทำสิ่งนี้ตามค่าเริ่มต้น