หัวกินอักขระพิเศษ


15

คำสั่ง shell ต่อไปนี้คาดว่าจะพิมพ์บรรทัดคี่ของอินพุตสตรีมเท่านั้น:

echo -e "aaa\nbbb\nccc\nddd\n" | (while true; do head -n 1; head -n 1 >/dev/null; done)

aaaแต่มันเป็นเพียงแค่พิมพ์บรรทัดแรก:

สิ่งเดียวกันจะไม่เกิดขึ้นเมื่อใช้กับตัวเลือก-c( --bytes):

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 >/dev/null; done)

คำสั่งนี้ส่งออก1234512345ตามที่คาดไว้ แต่นี้จะทำงานเฉพาะในcoreutilsการดำเนินงานของheadยูทิลิตี้ busybox12345การดำเนินงานยังคงกินอักขระพิเศษเพื่อการส่งออกเป็นเพียง

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

ไม่สามารถพูดแบบเดียวกันสำหรับกรณีได้เมื่อใช้--bytesตัวเลือก ในกรณีนี้คุณรู้จำนวนไบต์ที่คุณต้องอ่าน ดังนั้นคุณอาจอ่านจำนวนไบต์นี้อย่างแน่นอนและไม่มากกว่านั้น corelibsการดำเนินการจะใช้โอกาสนี้ แต่busyboxหนึ่งไม่ก็ยังคงอ่านไบต์มากขึ้นกว่าที่จำเป็นเข้าไปในกันชน มันอาจทำเพื่อทำให้การใช้งานง่ายขึ้น

ดังนั้นคำถาม ถูกต้องหรือไม่ที่headยูทิลิตี้ใช้อักขระจากอินพุตสตรีมมากกว่าที่ถาม มีมาตรฐานสำหรับยูทิลิตี้ Unix บ้างไหม? และถ้ามีมันระบุพฤติกรรมนี้หรือไม่?

PS

คุณต้องกดCtrl+Cเพื่อหยุดคำสั่งด้านบน สาธารณูปโภค Unix EOFไม่ล้มเหลวในการอ่านเกิน หากคุณไม่ต้องการกดคุณอาจใช้คำสั่งที่ซับซ้อนมากขึ้น:

echo 12345678901234567890 | (while true; do head -c 5; head -c 5 | [ `wc -c` -eq 0 ] && break >/dev/null; done)

ซึ่งฉันไม่ได้ใช้เพื่อความเรียบง่าย


2
Neardupe unix.stackexchange.com/questions/48777/...และunix.stackexchange.com/questions/84011/... นอกจากนี้หากชื่อเรื่องนี้มีอยู่ในภาพยนตร์XคำตอบของฉันคือZardoz :)
dave_thompson_085

คำตอบ:


30

มันถูกต้องหรือไม่สำหรับยูทิลิตี้ส่วนหัวเพื่อใช้อักขระจากอินพุตสตรีมมากกว่าที่ถาม

ใช่ได้รับอนุญาต (ดูด้านล่าง)

มีมาตรฐานสำหรับยูทิลิตี้ Unix บ้างไหม?

ใช่ปริมาณ POSIX 3 เชลล์และสาธารณูปโภค

และถ้ามีมันระบุพฤติกรรมนี้หรือไม่?

มันทำในการแนะนำ:

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

headเป็นหนึ่งในยูทิลิตี้มาตรฐานดังนั้นการใช้งานที่สอดคล้องกับ POSIX จะต้องใช้พฤติกรรมที่อธิบายไว้ข้างต้น

GNU head ไม่พยายามที่จะปล่อยอธิบายไฟล์ในตำแหน่งที่ถูกต้อง แต่มันเป็นไปไม่ได้ที่จะแสวงหาท่อดังนั้นในการทดสอบของคุณก็ไม่สามารถเรียกคืนตำแหน่ง คุณสามารถดูสิ่งนี้ได้โดยใช้strace:

$ echo -e "aaa\nbbb\nccc\nddd\n" | strace head -n 1
...
read(0, "aaa\nbbb\nccc\nddd\n\n", 8192) = 17
lseek(0, -13, SEEK_CUR)                 = -1 ESPIPE (Illegal seek)
...

readผลตอบแทน 17 ไบต์ (ทั้งหมดเข้าที่มี) headประมวลผลสี่เหล่านั้นและจากนั้นก็พยายามที่จะย้ายกลับ 13 ไบต์ แต่ก็ไม่สามารถทำได้ (คุณจะเห็นได้ว่า GNU headใช้บัฟเฟอร์ 8 KiB)

เมื่อคุณบอกheadให้นับจำนวนไบต์ (ซึ่งไม่เป็นมาตรฐาน) จะรู้จำนวนไบต์ที่จะอ่านดังนั้นจึงสามารถ (ถ้าใช้วิธีนั้น) จำกัด การอ่านตามนั้น นี่คือเหตุผลที่head -c 5การทดสอบของคุณใช้งานได้: GNU headอ่านเพียงห้าไบต์เท่านั้นดังนั้นจึงไม่จำเป็นต้องค้นหาเพื่อเรียกคืนตำแหน่งของตัวอธิบายไฟล์

หากคุณเขียนเอกสารไปยังไฟล์และใช้สิ่งนั้นแทนคุณจะได้รับพฤติกรรมที่ตามมา:

$ echo -e "aaa\nbbb\nccc\nddd\n" > file
$ < file (while true; do head -n 1; head -n 1 >/dev/null; done)
aaa
ccc

2
หนึ่งสามารถใช้line(ตอนนี้ลบออกจาก POSIX / XPG แต่ยังคงมีอยู่ในระบบหลายระบบ) หรือread( IFS= read -r line) ยูทิลิตี้แทนซึ่งอ่านทีละหนึ่งไบต์เพื่อหลีกเลี่ยงปัญหา
Stéphane Chazelas

3
โปรดทราบว่าไม่ว่าhead -c 5จะอ่าน 5 ไบต์หรือบัฟเฟอร์เต็มรูปแบบขึ้นอยู่กับการใช้งาน (โปรดทราบว่าhead -cไม่เป็นมาตรฐาน) คุณไม่สามารถวางใจได้ คุณต้องdd bs=1 count=5มีการรับประกันว่าจะอ่านได้ไม่เกิน 5 ไบต์
Stéphane Chazelas

ขอบคุณ @ Stéphaneฉันได้อัปเดต-c 5คำอธิบายแล้ว
Stephen Kitt

โปรดทราบว่าheadบิวด์อินของksh93อ่านหนึ่งไบต์ในแต่ละครั้งhead -n 1เมื่อไม่สามารถค้นหาอินพุตได้
Stéphane Chazelas

1
@anton_rh ddทำงานได้อย่างถูกต้องกับท่อด้วยbs=1ถ้าคุณใช้ a countเนื่องจากการอ่านบน pipes อาจส่งคืนน้อยกว่าที่ร้องขอ (แต่อย่างน้อยหนึ่งไบต์เว้นแต่จะถึง eof) GNU ddมีiflag=fullblockสิ่งนั้นที่สามารถบรรเทาได้
Stéphane Chazelas

6

จาก POSIX

หัวยูทิลิตี้จะคัดลอกไฟล์ข้อมูลในการออกมาตรฐานสิ้นสุดการส่งออกสำหรับแต่ละไฟล์ตรงจุดที่กำหนด

ไม่ได้พูดอะไรเกี่ยวกับปริมาณที่head ต้องอ่านจากอินพุต ความต้องการให้อ่านไบต์ต่อไบต์จะโง่เพราะมันจะช้ามากในกรณีส่วนใหญ่

อย่างไรก็ตามนี่คือที่อยู่ในreadbuiltin / ยูทิลิตี้: เปลือกทั้งหมดที่ฉันสามารถหาreadจากท่อหนึ่งไบต์ในเวลาและข้อความมาตรฐานสามารถตีความได้หมายความว่าสิ่งนี้จะต้องทำเพื่อให้สามารถอ่านเพียงหนึ่งบรรทัด:

อ่านยูทิลิตี้จะอ่านบรรทัดตรรกะเดียวจากอินพุตมาตรฐานเป็นหนึ่งหรือมากกว่าตัวแปรเปลือก

ในกรณีของreadซึ่งใช้ในเชลล์สคริปต์กรณีการใช้งานทั่วไปจะเป็นดังนี้:

read someline
if something ; then 
    someprogram ...
fi

นี่เข้ามาตรฐานของการsomeprogramเป็นเช่นเดียวกับที่ของเปลือก แต่ก็สามารถที่คาดว่าsomeprogramจะได้รับในการอ่านทุกอย่างที่เกิดขึ้นหลังจากที่สายการป้อนค่าแรกบริโภคโดยไม่ได้รับสิ่งที่เหลือหลังจากบัฟเฟอร์อ่านโดยread readในทางกลับกันการใช้headในตัวอย่างของคุณเป็นเรื่องแปลกมาก


หากคุณต้องการลบทุกบรรทัดอื่น ๆ มันจะดีกว่า (และเร็วกว่า) ในการใช้เครื่องมือบางอย่างที่สามารถจัดการอินพุตทั้งหมดได้ในครั้งเดียวเช่น

$ seq 1 10 | sed -ne '1~2p'   # GNU sed
$ seq 1 10 | sed -e 'n;d'     # works in GNU sed and the BSD sed on macOS

$ seq 1 10 | awk 'NR % 2' 
$ seq 1 10 | perl -ne 'print if $. % 2'

แต่ดูส่วน "อินพุตไฟล์" ของการแนะนำ POSIX ในเล่ม 3 ...
สตีเฟ่น Kitt

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

2
โปรดทราบว่าถ้าคุณใช้-r, readอาจจะอ่านมากกว่าหนึ่งบรรทัด (ไม่IFS=มันก็จะดึงชั้นนำและช่องว่างต่อท้ายและแท็บ (มีค่าเริ่มต้นของ$IFS))
Stéphane Chazelas

@AlexP ใช่สตีเฟ่นเพิ่งเชื่อมโยงส่วนนั้น
ilkkachu

โปรดทราบว่าheadบิวด์อินของksh93อ่านหนึ่งไบต์ในแต่ละครั้งhead -n 1เมื่อไม่สามารถค้นหาอินพุตได้
Stéphane Chazelas

1
awk '{if (NR%2) == 1) print;}'

Hellóka :-) และยินดีต้อนรับสู่เว็บไซต์! หมายเหตุเราต้องการคำตอบที่ละเอียดยิ่งขึ้น พวกเขาควรจะมีประโยชน์สำหรับ googler แห่งอนาคต
peterh - Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.