เครื่องมือทุบตีเพื่อรับบรรทัดที่ n จากไฟล์


604

มีวิธี "แบบบัญญัติ" หรือไม่? ฉันใช้head -n | tail -1ซึ่งทำเคล็ดลับ แต่ฉันสงสัยว่ามีเครื่องมือทุบตีที่แยกเส้น (หรือช่วงบรรทัด) จากไฟล์

โดย "canonical" ฉันหมายถึงโปรแกรมที่มีหน้าที่หลักทำเช่นนั้น


10
"วิธี Unix" คือการเชื่อมโยงเครื่องมือที่ทำงานของตนได้ดี ดังนั้นฉันคิดว่าคุณพบวิธีที่เหมาะสมแล้ว วิธีการอื่นรวมถึงawkและsedและฉันแน่ใจว่าบางคนสามารถเกิดขึ้นกับ Perl หนึ่งซับหรือดังนั้น;)
0xC0000022L

3
คำสั่งสองครั้งแสดงให้เห็นว่าการhead | tailแก้ปัญหาเป็นแบบย่อยที่ดีที่สุด มีการเสนอแนวทางแก้ไขปัญหาอื่นที่เหมาะสมที่สุด
Jonathan Leffler

คุณใช้มาตรฐานใดในการแก้ปัญหาที่เร็วที่สุดสำหรับกรณีทั่วไปหรือไม่?
Marcin

5
มาตรฐาน (สำหรับช่วง) ที่บรรทัดแมว X กับสาย Y ในไฟล์ขนาดใหญ่บนUnix และ Linux (cc @Marcin ในกรณีที่คุณยังสงสัยหลังจากสองปี)
Kevin

6
head | tailแก้ปัญหาไม่ทำงานถ้าคุณสอบถามสายที่ไม่ได้อยู่ในการป้อนข้อมูล: มันจะพิมพ์บรรทัดสุดท้าย
jarno

คำตอบ:


801

headและไปป์ด้วยtailจะช้าสำหรับไฟล์ขนาดใหญ่ ฉันอยากจะแนะนำsedสิ่งนี้:

sed 'NUMq;d' file

ที่ไหนNUMคือจำนวนของเส้นที่คุณต้องการพิมพ์; ดังนั้นสำหรับตัวอย่างเช่นsed '10q;d' fileการพิมพ์เส้น file10

คำอธิบาย:

NUMqNUMจะลาออกทันทีเมื่อจำนวนบรรทัดคือ

dจะลบบรรทัดแทนการพิมพ์; สิ่งนี้จะถูกยับยั้งในบรรทัดสุดท้ายเนื่องจากqสาเหตุที่เหลือของสคริปต์จะถูกข้ามเมื่อออกจาก

หากคุณมีNUMตัวแปรคุณจะต้องใช้เครื่องหมายคำพูดคู่แทนคำเดียว:

sed "${NUM}q;d" file

44
สำหรับผู้ที่สงสัยวิธีนี้ดูเหมือนจะเร็วกว่าโซลูชั่นsed -n 'NUMp'และประมาณ 6 ถึง 9 เท่าที่sed 'NUM!d'เสนอด้านล่าง
Skippy le Grand Gourou

75
ฉันคิดว่าtail -n+NUM file | head -n1น่าจะเร็วหรือเร็วกว่า อย่างน้อยมันก็เร็วขึ้นอย่างมากในระบบของฉันเมื่อฉันลองด้วย NUM เป็น 250000 ในไฟล์ที่มีครึ่งล้านบรรทัด YMMV แต่ฉันไม่เห็นว่าทำไมมันถึงเป็นเช่นนั้น
rici

2
@rici (ปรับปรุงความคิดเห็นก่อนหน้า) บน Linux (Ubuntu 12.04, Fedora 20) ใช้catเป็นที่แน่นอนได้เร็วขึ้น (เกือบสองเท่าที่รวดเร็ว) แต่เฉพาะในกรณีที่ไฟล์ไม่ได้รับการเก็บไว้ชั่วคราวเลย เมื่อไฟล์ถูกแคชแล้วการใช้อาร์กิวเมนต์ของชื่อไฟล์โดยตรงจะเร็วกว่า (เร็วกว่าประมาณ 1/3) ในขณะที่catประสิทธิภาพยังคงเหมือนเดิม อยากรู้อยากเห็นใน OS X 10.9.3 สิ่งนี้ไม่ได้สร้างความแตกต่างใด ๆ : cat/ ไม่catไฟล์แคชหรือไม่ @ anubhava: ความสุขของฉัน
mklement0

2
@SkippyleGrandGourou: ที่กำหนดลักษณะเฉพาะของการเพิ่มประสิทธิภาพนี้แม้คุณช่วงของตัวเลขจะไม่มีจุดหมายเป็นคำสั่งทั่วไป สิ่งเดียวที่เป็นเรื่องทั่วไปคือ: (a) การเพิ่มประสิทธิภาพนี้สามารถนำไปใช้กับอินพุตทั้งหมดได้อย่างปลอดภัย (b) เอฟเฟกต์จะอยู่ในช่วงตั้งแต่ไม่มีจนถึงละครทั้งนี้ขึ้นอยู่กับดัชนีของเส้นที่ค้นหาเทียบกับจำนวนบรรทัดทั้งหมด
mklement0

17
sed 'NUMqจะส่งออกNUMไฟล์แรกและ;dจะลบทั้งหมดยกเว้นบรรทัดสุดท้าย
anubhava

304
sed -n '2p' < file.txt

จะพิมพ์บรรทัดที่ 2

sed -n '2011p' < file.txt

บรรทัดที่ 2011

sed -n '10,33p' < file.txt

บรรทัด 10 ถึงบรรทัด 33

sed -n '1p;3p' < file.txt

บรรทัดที่ 1 และ 3

และอื่น ๆ ...

สำหรับการเพิ่มบรรทัดที่มี sed คุณสามารถตรวจสอบสิ่งนี้:

sed: แทรกบรรทัดในตำแหน่งที่แน่นอน


6
@RafaelBarbosa <ในกรณีนี้ไม่จำเป็น เพียงแค่มันเป็นความชอบของฉันโดยใช้การเปลี่ยนเส้นทางเพราะฉันมักจะใช้การเปลี่ยนเส้นทางเช่นsed -n '100p' < <(some_command)- ดังนั้นไวยากรณ์สากล :) มันไม่ได้มีประสิทธิภาพน้อยลงเพราะการเปลี่ยนเส้นทางจะทำกับเชลล์เมื่อฟอร์กตัวเองดังนั้น ... มันเป็นเพียงการตั้งค่า ... (และใช่มันเป็นตัวละครอีกต่อไป) :)
jm666

1
@ jm666 ที่จริงแล้วมันมีความยาว 2 ตัวอักษรเพราะปกติแล้วคุณจะใส่ '<' และพื้นที่พิเศษ '' หลังจาก <ตรงข้ามกับที่ว่างเพียงช่องเดียวถ้าคุณไม่ได้ใช้ <:)
rasen58

2
@ rasen58 ช่องว่างก็เป็นตัวละครเช่นกัน? :) / โอเคล้อเล่น - คุณพูดถูก / :)
jm666

1
@ แน่นอนว่าถ้ามีคนต้องการเพิ่มประสิทธิภาพ แต่ IMHO สำหรับปัญหา "ทั่วไป" ก็โอเคและความแตกต่างนั้นไม่สามารถสังเกตเห็นได้ นอกจากนี้head/ tailไม่สามารถแก้ไขsed -n '1p;3p'สถานการณ์ - aka พิมพ์แถวที่ไม่ติดกันมากขึ้น ...
jm666

1
@duhaime แน่นอน - บันทึกถูกต้องและจำเป็น :)
jm666

93

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

ติดตั้ง

ฉันมีไฟล์ข้อมูลข้อความ ASCII 3.261 กิกะไบต์ด้วยหนึ่งคู่ค่าคีย์ต่อแถว ไฟล์มีจำนวนทั้งหมด 3,339,550,320 แถวและเปิดอย่างหวุดหวิดในเครื่องมือแก้ไขใด ๆ ที่ฉันได้ลองรวมถึง Go-to Vim ของฉัน ฉันต้องเซ็ตย่อยไฟล์นี้เพื่อตรวจสอบค่าบางอย่างที่ฉันค้นพบเริ่มต้นรอบแถวเท่านั้น ~ 500,000,000

เนื่องจากไฟล์มีหลายแถว:

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

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

สำหรับจุดประสงค์ของการมีสติของฉันฉันจะไม่พยายามอ่านเต็ม 500,000,000 บรรทัดที่ฉันต้องการสำหรับปัญหาของตัวเอง แต่ฉันจะพยายามแยกแถว 50,000,000 ออกจาก 3,339,550,320 (ซึ่งหมายความว่าการอ่านไฟล์เต็มจะใช้เวลานานกว่าที่จำเป็น 60x)

ฉันจะใช้timeบิวด์อินเพื่อเปรียบเทียบมาตรฐานแต่ละคำสั่ง

พื้นฐาน

ก่อนอื่นเรามาดูวิธีการhead tailแก้ปัญหา:

$ time head -50000000 myfile.ascii | tail -1
pgm_icnt = 0

real    1m15.321s

ค่าพื้นฐานสำหรับแถวที่ 50 ล้านคือ 00: 01: 15.321 ถ้าฉันไปตรงแถวที่ 500 ล้านมันอาจจะเป็น ~ 12.5 นาที

ตัด

ฉันสงสัยเรื่องนี้ แต่มันก็คุ้มค่ากับการยิง:

$ time cut -f50000000 -d$'\n' myfile.ascii
pgm_icnt = 0

real    5m12.156s

อันนี้ใช้เวลา 00: 05: 12.156 ในการวิ่งซึ่งช้ากว่าพื้นฐานมาก! ฉันไม่แน่ใจว่าจะอ่านไฟล์ทั้งไฟล์หรือสูงถึง 50 ล้านบรรทัดก่อนที่จะหยุดทำงาน แต่ไม่ว่าจะเป็นการแก้ปัญหาที่ทำงานได้จริงหรือไม่

AWK

ฉันรันโซลูชันด้วยexitเพราะฉันจะไม่รอให้ไฟล์เต็มรูปแบบทำงาน:

$ time awk 'NR == 50000000 {print; exit}' myfile.ascii
pgm_icnt = 0

real    1m16.583s

รหัสนี้ทำงานใน 00: 01: 16.583 ซึ่งช้ากว่า ~ 1 วินาทีเท่านั้น แต่ก็ยังไม่พัฒนาในระดับพื้นฐาน ในอัตรานี้หากคำสั่ง exit ถูกแยกออกมันอาจใช้เวลาประมาณ 76 นาทีในการอ่านไฟล์ทั้งหมด!

Perl

ฉันใช้โซลูชัน Perl ที่มีอยู่เช่นกัน:

$ time perl -wnl -e '$.== 50000000 && print && exit;' myfile.ascii
pgm_icnt = 0

real    1m13.146s

รหัสนี้วิ่งใน 00: 01: 13.146 ซึ่งเร็วกว่า baseline ประมาณ 2 วินาที หากฉันใช้เต็ม 500,000,000 อาจใช้เวลาประมาณ 12 นาที

sed

คำตอบที่ดีที่สุดบนกระดานนี่คือผลลัพธ์ของฉัน:

$ time sed "50000000q;d" myfile.ascii
pgm_icnt = 0

real    1m12.705s

รหัสนี้ทำงานใน 00: 01: 12.705 ซึ่งเร็วกว่า baseline 3 วินาทีและเร็วกว่า Perl ~ 0.4 วินาที ถ้าฉันวิ่งเต็ม 500,000,000 แถวมันคงจะใช้เวลาประมาณ 12 นาที

mapfile

ฉันทุบตี 3.1 และดังนั้นจึงไม่สามารถทดสอบวิธีแก้ปัญหา mapfile

ข้อสรุป

ดูเหมือนว่าส่วนใหญ่เป็นการยากที่จะปรับปรุงhead tailวิธีการแก้ปัญหา sedทางออกที่ดีที่สุดให้เพิ่มขึ้น ~ 3% ในประสิทธิภาพ

(เปอร์เซ็นต์ที่คำนวณด้วยสูตร% = (runtime/baseline - 1) * 100)

แถว 50,000,000

  1. 00: 01: 12.705 (-00: 00: 02.616 = -3.47%) sed
  2. 00: 01: 13.146 (-00: 00: 02.175 = -2.89%) perl
  3. 00: 01: 15.321 (+00: 00: 00.000 = + 0.00%) head|tail
  4. 00: 01: 16.583 (+00: 00: 01.262 = + 1.68%) awk
  5. 00: 05: 12.156 (+00: 03: 56.835 = + 314.43%) cut

แถว 500,000,000

  1. 00: 12: 07.050 (-00: 00: 26.160) sed
  2. 00: 12: 11.460 (-00: 00: 21.750) perl
  3. 00: 12: 33.210 (+00: 00: 00.000) head|tail
  4. 00: 12: 45.830 (+00: 00: 12.620) awk
  5. 00: 52: 01.560 (+00: 40: 31.650) cut

แถว 3,338,559,320

  1. 01: 20: 54.599 (-00: 03: 05.327) sed
  2. 01: 21: 24.045 (-00: 02: 25.227) perl
  3. 01: 23: 49.273 (+00: 00: 00.000) head|tail
  4. 01: 25: 13.548 (+00: 02: 35.735) awk
  5. 05: 47: 23.026 (+04: 24: 26.246) cut

4
ฉันสงสัยว่าการใช้ไฟล์ทั้งหมดใน / dev / null จะใช้เวลานานเท่าใด (จะเกิดอะไรขึ้นถ้านี่เป็นเพียงมาตรฐานฮาร์ดดิสก์)
sanmai

ฉันรู้สึกอยากยั่วยุให้คุณเป็นเจ้าของพจนานุกรมไฟล์ข้อความขนาด 3+ กิ๊ก ไม่ว่าจะด้วยเหตุผลอะไรก็ตามสิ่งนี้ก็รวมเอาข้อความเดิมเข้าไว้ด้วย :)
Stabledog

51

ด้วยawkมันค่อนข้างเร็ว:

awk 'NR == num_line' file

เมื่อเป็นเช่นนี้เป็นความจริงการทำงานเริ่มต้นของการดำเนินการ:awk{print $0}


รุ่นทางเลือก

หากไฟล์ของคุณมีขนาดใหญ่มากคุณควรexitอ่านบรรทัดที่ต้องการให้ดีขึ้น วิธีนี้คุณประหยัดเวลา CPU เปรียบเทียบเวลาดูในตอนท้ายของคำตอบ

awk 'NR == num_line {print; exit}' file

หากคุณต้องการให้หมายเลขบรรทัดจากตัวแปร bash คุณสามารถใช้:

awk 'NR == n' n=$num file
awk -v n=$num 'NR == n' file   # equivalent

ดูว่ามีการประหยัดเวลามากน้อยเพียงใดโดยexitเฉพาะอย่างยิ่งหากบรรทัดเกิดขึ้นในส่วนแรกของไฟล์:

# Let's create a 10M lines file
for ((i=0; i<100000; i++)); do echo "bla bla"; done > 100Klines
for ((i=0; i<100; i++)); do cat 100Klines; done > 10Mlines

$ time awk 'NR == 1234567 {print}' 10Mlines
bla bla

real    0m1.303s
user    0m1.246s
sys 0m0.042s
$ time awk 'NR == 1234567 {print; exit}' 10Mlines
bla bla

real    0m0.198s
user    0m0.178s
sys 0m0.013s

ดังนั้นความแตกต่างคือ 0.198 กับ 1.303 วินาทีเร็วขึ้น 6x เท่า


วิธีนี้จะช้าลงเสมอเพราะ awk พยายามแยกฟิลด์ สามารถลดค่าใช้จ่ายในการแบ่งฟิลด์ได้awk 'BEGIN{FS=RS}(NR == num_line) {print; exit}' file
kvantour

อำนาจที่แท้จริงของ awk ในวิธีการนี้ออกมาเมื่อคุณต้องการ n1 บรรทัด concatenate ของ file1, N2 ของ file2, N3 หรือ file3 awk 'FNR==n' n=10 file1 n=30 file2 n=60 file3... ด้วย GNU awk awk 'FNR==n{print;nextfile}' n=10 file1 n=30 file2 n=60 file3นี้สามารถเร่งความเร็วขึ้นโดยใช้
kvantour

@kvantour แน่นอน nextfile ของ GNU awk นั้นยอดเยี่ยมสำหรับสิ่งต่าง ๆ ทำไมต้องFS=RSหลีกเลี่ยงการแยกฟิลด์?
fedorqui 'ดังนั้นหยุดทำอันตราย'

1
FS=RSไม่ได้หลีกเลี่ยงการแยกฟิลด์ แต่แยกวิเคราะห์ $ 0 เท่านั้นและกำหนดหนึ่งฟิลด์เนื่องจากไม่มีRSใน$0
kvantour

@kvantour ฉันได้ทำการทดสอบกับFS=RSและไม่เห็นความแตกต่างในการกำหนดเวลา แล้วฉันจะถามคำถามเกี่ยวกับเรื่องนี้อย่างไรเพื่อให้คุณสามารถขยายได้ ขอบคุณ!
fedorqui 'ดังนั้นหยุดทำอันตราย'

29

จากการทดสอบของฉันในแง่ของประสิทธิภาพและความสามารถในการอ่านคำแนะนำของฉันคือ:

tail -n+N | head -1

Nคือหมายเลขบรรทัดที่คุณต้องการ ตัวอย่างเช่นtail -n+7 input.txt | head -1จะพิมพ์บรรทัดที่ 7 ของไฟล์

tail -n+Nจะพิมพ์ทุกอย่างที่เริ่มต้นจากบรรทัดNและhead -1จะทำให้หยุดหลังจากหนึ่งบรรทัด


ทางเลือกhead -N | tail -1นั้นอาจจะอ่านง่ายขึ้นเล็กน้อย ตัวอย่างเช่นสิ่งนี้จะพิมพ์บรรทัดที่ 7:

head -7 input.txt | tail -1

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

คะแนนsed 'NUMq;d'สูงสุดน่าสนใจที่จะรู้ แต่ฉันจะเถียงว่าจะมีคนน้อยกว่าที่เข้าใจได้จากกล่องกว่าโซลูชันหัว / ท้ายและมันก็ช้ากว่าหาง / หัว

ในการทดสอบของฉันทั้งรุ่นก้อย / หัวมีประสิทธิภาพสูงกว่าsed 'NUMq;d'อย่างสม่ำเสมอ นั่นเป็นไปตามมาตรฐานอื่น ๆ ที่โพสต์ มันยากที่จะหาเคสที่ก้อย / หัวแย่จริงๆ นอกจากนี้ยังไม่น่าแปลกใจเนื่องจากการดำเนินการเหล่านี้เป็นสิ่งที่คุณคาดว่าจะได้รับการปรับให้เหมาะสมที่สุดในระบบ Unix ที่ทันสมัย

หากต้องการทราบข้อมูลเกี่ยวกับความแตกต่างด้านประสิทธิภาพนี่เป็นหมายเลขที่ฉันได้รับสำหรับไฟล์ขนาดใหญ่ (9.3G):

  • tail -n+N | head -1: 3.7 วินาที
  • head -N | tail -1: 4.6 วินาที
  • sed Nq;d: 18.8 วินาที

ผลลัพธ์อาจแตกต่างกัน แต่ประสิทธิภาพhead | tailและtail | headโดยทั่วไปสามารถเปรียบเทียบได้กับอินพุตที่เล็กกว่าและsedมักจะช้าลงด้วยปัจจัยที่สำคัญ (ประมาณ 5x หรือมากกว่านั้น)

ในการทำซ้ำมาตรฐานของฉันคุณสามารถลองทำสิ่งต่อไปนี้ แต่ได้รับคำเตือนว่ามันจะสร้างไฟล์ 9.3G ในไดเรกทอรีการทำงานปัจจุบัน:

#!/bin/bash
readonly file=tmp-input.txt
readonly size=1000000000
readonly pos=500000000
readonly retries=3

seq 1 $size > $file
echo "*** head -N | tail -1 ***"
for i in $(seq 1 $retries) ; do
    time head "-$pos" $file | tail -1
done
echo "-------------------------"
echo
echo "*** tail -n+N | head -1 ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time tail -n+$pos $file | head -1
done
echo "-------------------------"
echo
echo "*** sed Nq;d ***"
echo

seq 1 $size > $file
ls -alhg $file
for i in $(seq 1 $retries) ; do
    time sed $pos'q;d' $file
done
/bin/rm $file

นี่คือผลลัพธ์ของการวิ่งบนเครื่องของฉัน (ThinkPad X1 Carbon พร้อม SSD และหน่วยความจำ 16G) ฉันคิดว่าในการทำงานครั้งสุดท้ายทุกอย่างจะมาจากแคชไม่ใช่จากดิสก์:

*** head -N | tail -1 ***
500000000

real    0m9,800s
user    0m7,328s
sys     0m4,081s
500000000

real    0m4,231s
user    0m5,415s
sys     0m2,789s
500000000

real    0m4,636s
user    0m5,935s
sys     0m2,684s
-------------------------

*** tail -n+N | head -1 ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:49 tmp-input.txt
500000000

real    0m6,452s
user    0m3,367s
sys     0m1,498s
500000000

real    0m3,890s
user    0m2,921s
sys     0m0,952s
500000000

real    0m3,763s
user    0m3,004s
sys     0m0,760s
-------------------------

*** sed Nq;d ***

-rw-r--r-- 1 phil 9,3G Jan 19 19:50 tmp-input.txt
500000000

real    0m23,675s
user    0m21,557s
sys     0m1,523s
500000000

real    0m20,328s
user    0m18,971s
sys     0m1,308s
500000000

real    0m19,835s
user    0m18,830s
sys     0m1,004s

1
ประสิทธิภาพแตกต่างกันระหว่างhead | tailvs tail | headหรือไม่ หรือมันขึ้นอยู่กับว่าบรรทัดใดที่จะถูกพิมพ์ (จุดเริ่มต้นของไฟล์เทียบกับจุดสิ้นสุดของไฟล์)?
wisbucky

1
@wuckyucky ฉันไม่มีตัวเลขที่แข็ง แต่ข้อเสียอย่างหนึ่งของการใช้หางตามมาด้วย "หัว -1" คือคุณต้องรู้ระยะเวลาล่วงหน้าทั้งหมด หากคุณไม่ทราบคุณจะต้องนับก่อนซึ่งจะเป็นการสูญเสียประสิทธิภาพ ข้อเสียอีกอย่างคือมันใช้งานง่ายกว่า ตัวอย่างเช่นหากคุณมีหมายเลข 1 ถึง 10 และคุณต้องการรับบรรทัดที่ 3 คุณจะต้องใช้ "tail -8 | head -1" นั่นเป็นข้อผิดพลาดที่เกิดขึ้นได้ง่ายกว่า "head -3 | tail -1"
Philipp Claßen

ขออภัยฉันควรมีตัวอย่างให้ชัดเจน VShead -5 | tail -1 tail -n+5 | head -1ที่จริงแล้วฉันพบคำตอบอีกข้อหนึ่งที่ทำการเปรียบเทียบการทดสอบและพบว่าtail | headเร็วขึ้น stackoverflow.com/a/48189289
wisbucky

1
@wiscucky ขอบคุณที่พูดถึงมัน! ฉันทำการทดสอบบางอย่างและต้องยอมรับว่ามันเร็วขึ้นเล็กน้อยโดยไม่ขึ้นอยู่กับตำแหน่งของเส้นจากสิ่งที่ฉันเห็น ระบุว่าฉันเปลี่ยนคำตอบของฉันและรวมถึงมาตรฐานในกรณีที่มีคนต้องการทำซ้ำ
Philipp Claßen

27

ว้าวเป็นไปได้ทั้งหมด!

ลองสิ่งนี้:

sed -n "${lineNum}p" $file

หรือหนึ่งในนั้นขึ้นอยู่กับรุ่นของ Awk:

awk  -vlineNum=$lineNum 'NR == lineNum {print $0}' $file
awk -v lineNum=4 '{if (NR == lineNum) {print $0}}' $file
awk '{if (NR == lineNum) {print $0}}' lineNum=$lineNum $file

( คุณอาจต้องลองnawkหรือgawkคำสั่ง )

มีเครื่องมือที่พิมพ์เฉพาะบรรทัดนั้นหรือไม่? ไม่ใช่หนึ่งในเครื่องมือมาตรฐาน อย่างไรก็ตามsedน่าจะใกล้เคียงที่สุดและใช้ง่ายที่สุด



21

คำถามนี้ถูกแท็ก Bash ต่อไปนี้เป็นวิธีทำ Bash (≥4): ใช้mapfileกับตัวเลือก-s(ข้าม) และ-n(นับ)

หากคุณต้องการรับบรรทัดที่ 42 ของไฟล์file:

mapfile -s 41 -n 1 ary < file

ณ จุดนี้คุณจะมีอาร์เรย์aryของฟิลด์ที่มีบรรทัดของfile(รวมถึงการขึ้นบรรทัดใหม่) ซึ่งเราได้ข้าม 41 บรรทัดแรก ( -s 41) และหยุดหลังจากอ่านหนึ่งบรรทัด ( -n 1) นั่นคือเส้นที่ 42 จริงๆ หากต้องการพิมพ์:

printf '%s' "${ary[0]}"

หากคุณต้องการช่วงของบรรทัดให้พูดช่วงที่ 42–666 (รวมอยู่ด้วย) และบอกว่าคุณไม่ต้องการทำคณิตศาสตร์ด้วยตัวเองและพิมพ์ลง stdout:

mapfile -s $((42-1)) -n $((666-42+1)) ary < file
printf '%s' "${ary[@]}"

หากคุณต้องการประมวลผลบรรทัดเหล่านี้ด้วยเช่นกันไม่สะดวกในการจัดเก็บบรรทัดใหม่ที่ตามมา ในกรณีนี้ใช้-tตัวเลือก (ตัดแต่ง):

mapfile -t -s $((42-1)) -n $((666-42+1)) ary < file
# do stuff
printf '%s\n' "${ary[@]}"

คุณสามารถมีฟังก์ชั่นทำเพื่อคุณ:

print_file_range() {
    # $1-$2 is the range of file $3 to be printed to stdout
    local ary
    mapfile -s $(($1-1)) -n $(($2-$1+1)) ary < "$3"
    printf '%s' "${ary[@]}"
}

ไม่มีคำสั่งภายนอกเฉพาะ Bash ในตัว!


11

คุณยังสามารถใช้การพิมพ์และออก:

sed -n '10{p;q;}' file   # print line 10

6
-nตัวเลือกปิดการใช้งานดำเนินการเริ่มต้นในการพิมพ์ทุกเส้นเป็นแน่นอนคุณจะได้พบโดยอย่างรวดเร็วในหน้าคน
tripleee

ในGNU คำตอบsedทั้งหมดsedนั้นเกี่ยวกับความเร็วเดียวกัน ดังนั้น (สำหรับGNU sed ) นี่เป็นsedคำตอบที่ดีที่สุดเนื่องจากจะช่วยประหยัดเวลาสำหรับไฟล์ขนาดใหญ่และค่าบรรทัดที่ nขนาดเล็ก
agc


6

ทางออกที่เร็วที่สุดสำหรับไฟล์ขนาดใหญ่มักจะเป็นไปตามหัว | โดยมีเงื่อนไขว่าระยะทางทั้งสอง:

  • จากจุดเริ่มต้นของไฟล์ไปยังบรรทัดเริ่มต้น ให้เรียกมันว่าS
  • ระยะทางจากบรรทัดสุดท้ายจนถึงจุดสิ้นสุดของไฟล์ ไม่ว่าจะเป็นE

เป็นที่รู้จัก. จากนั้นเราสามารถใช้สิ่งนี้:

mycount="$E"; (( E > S )) && mycount="+$S"
howmany="$(( endline - startline + 1 ))"
tail -n "$mycount"| head -n "$howmany"

howmany เป็นเพียงการนับบรรทัดที่ต้องการ

รายละเอียดเพิ่มเติมบางส่วนในhttps://unix.stackexchange.com/a/216614/79743


1
โปรดอธิบายหน่วยของSและE(เช่นไบต์ตัวอักษรหรือบรรทัด)
agc

6

คำตอบทั้งหมดข้างต้นตอบคำถามโดยตรง แต่นี่เป็นวิธีแก้ปัญหาที่น้อยกว่าโดยตรง แต่เป็นความคิดที่สำคัญกว่าเพื่อกระตุ้นความคิด

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

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

เช่นคุณอาจสร้างรายการตำแหน่งอักขระสำหรับการขึ้นบรรทัดใหม่:

awk 'BEGIN{c=0;print(c)}{c+=length()+1;print(c+1)}' file.txt > file.idx

แล้วอ่านด้วยtailซึ่งจริงๆแล้วseekโดยตรงไปยังจุดที่เหมาะสมในไฟล์!

เช่นรับสาย 1,000:

tail -c +$(awk 'NR=1000' file.idx) file.txt | head -1
  • สิ่งนี้อาจใช้ไม่ได้กับอักขระ 2 ไบต์ / หลายไบต์เนื่องจาก awk เป็น "ตัวอักษรที่รับรู้" แต่ส่วนท้ายไม่ได้
  • ฉันไม่ได้ทดสอบกับไฟล์ขนาดใหญ่
  • ดูคำตอบนี้ด้วย
  • อีกทางเลือกหนึ่ง - แยกไฟล์ของคุณเป็นไฟล์ขนาดเล็ก!

5

ในฐานะที่เป็นผู้ติดตามการตอบสนองการเปรียบเทียบที่เป็นประโยชน์ของ CaffeineConnoisseur ... ฉันอยากรู้ว่าวิธีการ 'mapfile' นั้นถูกเปรียบเทียบกับคนอื่น ๆ อย่างรวดเร็วแค่ไหน (อย่างที่ไม่ได้ทดสอบ) ดังนั้นฉันจึงลองเปรียบเทียบความเร็วที่รวดเร็วและสกปรกด้วยตัวเอง ฉันมีทุบตี 4 มีประโยชน์ ลองทดสอบวิธี "tail | head" (แทนที่จะเป็น head | tail) ที่กล่าวถึงในความคิดเห็นหนึ่งในคำตอบยอดนิยมขณะที่ฉันอยู่ที่นั่นขณะที่คนกำลังร้องเพลงสรรเสริญ ฉันไม่ได้ใช้ไฟล์ทดสอบขนาดเท่าไหร่ สิ่งที่ดีที่สุดที่ฉันสามารถหาได้จากการแจ้งเตือนสั้น ๆ คือไฟล์สายเลือด 14M (เส้นยาวที่คั่นด้วยช่องว่างเพียง 12000 บรรทัด)

เวอร์ชั่นย่อ: mapfile ปรากฏเร็วกว่าวิธีการตัด แต่ช้ากว่าทุกอย่างดังนั้นฉันจะเรียกมันว่า dud หาง | หัว OTOH ดูเหมือนว่าจะเร็วที่สุดแม้ว่าจะมีขนาดเท่านี้ แต่ความแตกต่างไม่ใช่สิ่งสำคัญเมื่อเทียบกับ sed

$ time head -11000 [filename] | tail -1
[output redacted]

real    0m0.117s

$ time cut -f11000 -d$'\n' [filename]
[output redacted]

real    0m1.081s

$ time awk 'NR == 11000 {print; exit}' [filename]
[output redacted]

real    0m0.058s

$ time perl -wnl -e '$.== 11000 && print && exit;' [filename]
[output redacted]

real    0m0.085s

$ time sed "11000q;d" [filename]
[output redacted]

real    0m0.031s

$ time (mapfile -s 11000 -n 1 ary < [filename]; echo ${ary[0]})
[output redacted]

real    0m0.309s

$ time tail -n+11000 [filename] | head -n1
[output redacted]

real    0m0.028s

หวังว่านี่จะช่วยได้!


4

ใช้สิ่งที่คนอื่นพูดถึงฉันต้องการให้นี่เป็นฟังก์ชันที่รวดเร็วและน่าสนใจใน bash shell ของฉัน

สร้างไฟล์: ~/.functions

เพิ่มไปยังเนื้อหา:

getline() { line=$1 sed $line'q;d' $2 }

จากนั้นเพิ่มลงในของคุณ~/.bash_profile:

source ~/.functions

ตอนนี้เมื่อคุณเปิดหน้าต่างทุบตีใหม่คุณสามารถเรียกใช้ฟังก์ชันได้ดังนี้

getline 441 myfile.txt


3

หากคุณมีหลายบรรทัดโดยคั่นด้วย \ n (โดยปกติคือบรรทัดใหม่) คุณสามารถใช้ 'ตัด' ได้เช่นกัน:

echo "$data" | cut -f2 -d$'\n'

คุณจะได้รับบรรทัดที่ 2 จากไฟล์ -f3ให้บรรทัดที่ 3


1
สามารถใช้เพื่อแสดงหลายบรรทัด: cat FILE | cut -f2,5 -d$'\n'จะแสดงบรรทัดที่ 2 และ 5 ของไฟล์ (แต่จะไม่รักษาระเบียบ)
Andriy Makukha

2

หากต้องการพิมพ์บรรทัดที่ n โดยใช้ sed พร้อมตัวแปรเป็นหมายเลขบรรทัด

a=4
sed -e $a'q:d' file

นี่คือแฟล็ก '-e' สำหรับการเพิ่มสคริปต์ไปยังคำสั่งที่จะดำเนินการ


2
โคลอนเป็นข้อผิดพลาดทางไวยากรณ์และควรเป็นเครื่องหมายอัฒภาค
สามคน

2

คำตอบที่ดีมากมายแล้ว ส่วนตัวฉันไปกับ awk ~/.bash_profileเพื่อความสะดวกสบายถ้าคุณใช้ทุบตีเพียงเพิ่มด้านล่างนี้เพื่อคุณ และในครั้งต่อไปที่คุณเข้าสู่ระบบ (หรือถ้าคุณได้รับ. bash_profile ของคุณหลังจากการอัปเดตนี้) คุณจะมีฟังก์ชั่น "nth" ใหม่ที่ดีที่สามารถใช้งานไฟล์ของคุณได้

ดำเนินการนี้หรือใส่ไว้ใน ~ / .bash_profile ของคุณ (หากใช้ bash) และเปิด bash ใหม่ (หรือดำเนินการsource ~/.bach_profile)

# print just the nth piped in line nth () { awk -vlnum=${1} 'NR==lnum {print; exit}'; }

จากนั้นเมื่อต้องการใช้งานเพียงแค่ท่อผ่านมัน เช่น,:

$ yes line | cat -n | nth 5 5 line


1

หลังจากการดูที่คำตอบด้านบนและมาตรฐานผมได้ใช้ฟังก์ชั่นผู้ช่วยเล็ก ๆ :

function nth {
    if (( ${#} < 1 || ${#} > 2 )); then
        echo -e "usage: $0 \e[4mline\e[0m [\e[4mfile\e[0m]"
        return 1
    fi
    if (( ${#} > 1 )); then
        sed "$1q;d" $2
    else
        sed "$1q;d"
    fi
}

โดยทั่วไปคุณสามารถใช้มันในสองแฟชั่น:

nth 42 myfile.txt
do_stuff | nth 42

0

ฉันได้ใส่คำตอบข้างต้นไว้ในสคริปต์ทุบตีสั้น ๆ ที่คุณสามารถใส่ลงในไฟล์ชื่อget.shและลิงค์ไปยัง/usr/local/bin/get(หรือชื่ออื่นที่คุณต้องการ)

#!/bin/bash
if [ "${1}" == "" ]; then
    echo "error: blank line number";
    exit 1
fi
re='^[0-9]+$'
if ! [[ $1 =~ $re ]] ; then
    echo "error: line number arg not a number";
    exit 1
fi
if [ "${2}" == "" ]; then
    echo "error: blank file name";
    exit 1
fi
sed "${1}q;d" $2;
exit 0

ตรวจสอบให้แน่ใจว่าสามารถใช้งานได้กับ

$ chmod +x get

เชื่อมโยงเพื่อให้พร้อมใช้งานบนPATHด้วย

$ ln -s get.sh /usr/local/bin/get

เพลิดเพลินไปกับความรับผิดชอบ!

P

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