ทำไม 'sed q' ทำงานแตกต่างกันเมื่ออ่านจากไปป์


25

ฉันสร้างไฟล์ทดสอบชื่อ 'ทดสอบ' ที่มีสิ่งต่อไปนี้:

xxx
yyy
zzz

ฉันรันคำสั่ง:

(sed '/y/ q'; echo aaa; cat) < test

และฉันได้รับ:

xxx
yyy
aaa
zzz

จากนั้นฉันก็วิ่ง:

cat test | (sed '/y/ q'; echo aaa; cat)

และได้รับ:

xxx
yyy
aaa

คำถาม

sedอ่านและพิมพ์จนกว่าจะพบบรรทัดที่มี 'y' จากนั้นหยุด ในกรณีแรก แต่ไม่ใช่ที่สองแมวอ่านและพิมพ์ส่วนที่เหลือ

ใครสามารถอธิบายปรากฏการณ์ที่อยู่เบื้องหลังพฤติกรรมที่แตกต่างนี้ได้

ฉันก็สังเกตเห็นว่ามันทำงานในลักษณะนี้ใน Ubuntu 16.04 และ Centos 6 แต่ใน Centos 7 คำสั่งไม่พิมพ์ 'zzz'


ฉันเดาว่าcat(ในเชลล์ย่อย) สามารถใช้ file descriptor อีกครั้งในกรณีแรกเนื่องจาก stdin ถูกผูกไว้กับไฟล์จริง ในกรณีที่สอง stdin มาจากไพพ์และไม่ใช่ไฟล์จริง ทราบว่ายังไม่ได้พิมพ์(sed '/y/ q'; echo aaa; cat) < <(cat test) zzz
Martin Nyolt

1
ตัวอย่างที่ง่ายกว่า: (head -n1; head -n1) < testและcat test | (head -n1; head -n1)
Martin Nyolt

คำตอบ:


22

เมื่อแฟ้มใส่เป็นseekable (เช่นการอ่านจากแฟ้มปกติ) หรือยกเลิก seekable (เช่นการอ่านจากท่อ) sed(และสาธารณูปโภคมาตรฐานอื่น ๆ ) จะทำงานแตกต่างกัน (อ่านINPUT FILESส่วนในการเชื่อมโยงนี้ )

อ้างอิงจากเอกสาร:

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

ดังนั้นใน:

(sed '/y/ q'; echo aaa; cat) < test

sedดำเนินการqคำสั่ง uit ก่อนถึง EOF ดังนั้นจึงปล่อยให้ออฟเซ็ตไฟล์ที่จุดเริ่มต้นของzzzบรรทัดดังนั้นcatสามารถพิมพ์บรรทัดที่เหลือต่อไปได้ (GNU sed ไม่สอดคล้องกับ POSIX ในบางเงื่อนไขดูด้านล่าง)

และดำเนินการต่อจากเอกสาร:

สำหรับไฟล์ที่ไม่สามารถค้นหาได้สถานะของไฟล์ออฟเซ็ตในคำอธิบายไฟล์ที่เปิดสำหรับไฟล์นั้นจะไม่ได้รับการระบุ

ในกรณีนี้พฤติกรรมจะไม่ได้รับการระบุ เครื่องมือมาตรฐานส่วนใหญ่รวมถึงsedจะใช้อินพุตให้มากที่สุด มันอ่านผ่านyyyสายและquit catโดยไม่ต้องคืนค่าการชดเชยแฟ้มดังนั้นไม่มีอะไรเหลือสำหรับ


GNU sedไม่สอดคล้องกับมาตรฐานขึ้นอยู่กับการใช้ stdio ของระบบและรุ่น glibc:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

ผลที่ได้รับมาจาก Mac OSX 10.11.6 เครื่องเสมือน Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19 ซึ่งทำงานบน Openstack พร้อมแบ็คเอนด์ CEPH

ในระบบเหล่านั้นคุณสามารถใช้-uตัวเลือกเพื่อให้บรรลุพฤติกรรมมาตรฐาน:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

และสำหรับท่อ:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

ซึ่งนำไปสู่ประสิทธิภาพที่ไม่มีประสิทธิภาพอย่างมากเพราะsedต้องอ่านทีละหนึ่งไบต์ เอาท์พุทบางส่วนจากstrace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...

1
สำหรับ GNU sedนั้นขึ้นอยู่กับการใช้ stdio ของระบบ บนระบบ GNU (ด้วย GNU libc) GNU sedจะเข้ากันได้กับexit()ไฟล์ที่จัดการโดย stdio
Stéphane Chazelas

@ StéphaneChazelas: จะตรวจสอบได้อย่างไร? ด้วย Centos ของฉัน 7.2, Ubuntu 14.04 VM, sedไม่เข้ากันได้, แล็ปท็อป manjaro ของฉันทำ, ทั้งหมดมีsed รุ่นเดียวกัน4.2.2
cuonglm

@ StéphaneChazelas: ดูเหมือนว่ามีอะไรบางอย่างเกิดขึ้นภายใต้ประทุน บนเครื่องเสมือนของฉันstrace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'แสดงให้เห็นว่าไม่lseek()ได้ดำเนินการในขณะที่ฉัน manjaro ถูกเรียกก่อนlseek() exit_group()
cuonglm

ฉันคิดว่ามันลงไปในเวอร์ชันของ GNU libc คุณสามารถทดสอบกับmain() { char buf[999]; gets(buf); }'โปรแกรม
Stéphane Chazelas

1
@ StéphaneChazelas: ยืนยันแล้ว VMs ของฉันทั้งสองมี 2.17 และ 2.19 ในขณะที่หนึ่งใน manjaro ของฉันคือ 2.23 นี่ถือว่าเป็นข้อผิดพลาด glibc หรือไม่? คุณมีข้อมูลเกี่ยวกับการเปลี่ยนแปลงระหว่างรุ่น glibc หรือไม่
cuonglm
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.