grep จากจุดสิ้นสุดของไฟล์ไปยังจุดเริ่มต้น


38

ฉันมีไฟล์ที่มีเส้นประมาณ 30.000.000 บรรทัด (การบัญชี Radius) และฉันต้องการค้นหาคู่สุดท้ายของรูปแบบที่กำหนด

คำสั่ง:

tac accounting.log | grep $pattern

ให้สิ่งที่ฉันต้องการ แต่ช้าเกินไปเพราะระบบปฏิบัติการจะต้องอ่านไฟล์ทั้งหมดก่อนแล้วจึงส่งไปที่ไพพ์

ดังนั้นฉันต้องการสิ่งที่รวดเร็วที่สามารถอ่านไฟล์จากบรรทัดสุดท้ายถึงบรรทัดแรก

คำตอบ:


44

tacช่วยได้ก็ต่อเมื่อคุณใช้grep -m 1(สมมติว่า GNU grep) grepหยุดหลังจากจับคู่แรกแล้ว:

tac accounting.log | grep -m 1 foo

จากman grep:

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  

ในตัวอย่างในคำถามของคุณทั้งสองtacและgrepจำเป็นต้องประมวลผลไฟล์ทั้งหมดดังนั้นการใช้tacจึงไม่มีประโยชน์

ดังนั้นหากคุณgrep -mไม่ได้ใช้อย่าใช้งานtacเลยเพียงแค่แยกเอาท์พุทของgrepเพื่อให้ได้นัดสุดท้าย:

grep foo accounting.log | tail -n 1 

อีกวิธีหนึ่งคือการใช้ Perl หรือภาษาสคริปต์อื่น ๆ ตัวอย่าง (ที่ไหน$pattern=foo):

perl -ne '$l=$_ if /foo/; END{print $l}' file

หรือ

awk '/foo/{k=$0}END{print k}' file

1
ฉันกำลังใช้แทคเพราะฉันต้องการค้นหานัดสุดท้ายของรูปแบบที่กำหนด การใช้คำแนะนำของคุณ "grep -m1" เวลาดำเนินการจะเริ่มจาก 0m0.597s ถึง 0m0.007s \ o / ขอบคุณทุกๆคน!
Hábner Costa

1
@ HábnerCostaยินดีต้อนรับคุณมาก ฉันเข้าใจว่าทำไมคุณถึงใช้tacจุดของฉันคือว่ามันไม่ได้ช่วยถ้าคุณยังใช้-mเนื่องจากไฟล์ยังคงต้องอ่านเต็มสองโปรแกรม tail -n 1มิฉะนั้นคุณก็สามารถค้นหาปรากฏและเก็บเพียงคนสุดท้ายที่ผมทำกับ
terdon

6
ทำไมคุณพูดว่า "tac [... ] ต้องประมวลผลไฟล์ทั้งหมด"? สิ่งแรกที่แทคทำคือหาจุดสิ้นสุดของไฟล์และอ่านบล็อกจากจุดสิ้นสุด คุณสามารถยืนยันตัวเองด้วย strace (1) เมื่อรวมกับgrep -mมันควรจะมีประสิทธิภาพมาก
camh

1
@camh เมื่อรวมกับgrep -mมันคือ OP ไม่ได้ใช้-mทั้ง grep และ tac กำลังประมวลผลทั้งหมด
terdon

คุณช่วยขยายความหมายของawkบรรทัดได้ไหม
Sopalajo de Arrierez

12

เหตุผลว่าทำไม

tac file | grep foo | head -n 1

ไม่หยุดที่คู่แรกเป็นเพราะการบัฟเฟอร์

โดยปกติแล้วhead -n 1จะออกหลังจากอ่านบรรทัด ดังนั้นgrepควรรับ SIGPIPE และออกเช่นกันทันทีที่มันเขียนบรรทัดที่สอง

แต่สิ่งที่เกิดขึ้นก็คือเนื่องจากเอาต์พุตของมันไม่ได้ไปที่เทอร์มินัลgrepบัฟเฟอร์มัน นั่นคือมันไม่ได้เขียนจนกว่ามันจะสะสมเพียงพอ (4096 bytes ในการทดสอบของฉันกับ grep GNU)

สิ่งที่หมายถึงคือgrepจะไม่ออกก่อนที่จะเขียนข้อมูล 8192 ไบต์ดังนั้นอาจมีบางบรรทัด

ด้วย GNU grepคุณสามารถทำให้มันออกได้เร็วขึ้นโดยใช้--line-bufferedซึ่งบอกให้เขียนบรรทัดทันทีที่พบโดยไม่คำนึงว่าจะไปที่เทอร์มินัลหรือไม่ ดังนั้นgrepจะออกเมื่อพบบรรทัดที่สอง

แต่ด้วย GNU grepคุณสามารถใช้-m 1แทนได้เช่น @terdon แสดงซึ่งดีกว่าเมื่อออกจากการแข่งขันนัดแรก

หากคุณgrepไม่ใช่ GNU grepคุณสามารถใช้sedหรือawkแทน แต่tac เป็นคำสั่ง GNU ผมสงสัยคุณจะพบว่าระบบที่มีtacที่grepไม่ได้เป็น grepGNU

tac file | sed "/$pattern/!d;q"                             # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE

บางระบบต้องtail -rทำสิ่งเดียวกันกับที่ GNU tacทำ

โปรดทราบว่าสำหรับไฟล์ปกติ (หาได้) tacและtail -rมีประสิทธิภาพเพราะอ่านไฟล์ย้อนหลังพวกเขาไม่เพียงแค่อ่านไฟล์ในหน่วยความจำอย่างเต็มที่ก่อนที่จะพิมพ์ย้อนกลับ (เป็นวิธีการที่@ slmหรือtacไฟล์ที่ไม่ใช่ปกติ) .

ในระบบที่ค่าtacมิได้tail -rที่มีตัวเลือกเดียวที่จะดำเนินการย้อนหลังอ่านด้วยมือกับการเขียนโปรแกรมภาษาเช่นperlหรือการใช้งาน:

grep -e "$pattern" file | tail -n1

หรือ:

sed "/$pattern/h;$!d;g" file

แต่นั่นหมายถึงการค้นหาการแข่งขันทั้งหมดและพิมพ์ครั้งสุดท้ายเท่านั้น


4

นี่เป็นวิธีแก้ปัญหาที่เป็นไปได้ที่จะค้นหาตำแหน่งของการเกิดครั้งแรกของรูปแบบจากล่าสุด:

tac -s "$pattern" -r accounting.log | head -n 1

สิ่งนี้ใช้ประโยชน์จาก-sและ-rสวิตช์tacซึ่งมีดังนี้:

-s, --separator=STRING
use STRING as the separator instead of newline

-r, --regex
interpret the separator as a regular expression

ยกเว้นคุณจะสูญเสียทุกสิ่งที่อยู่ระหว่างจุดเริ่มต้นของบรรทัดและรูปแบบ
ychaouche

2

การใช้ sed

แสดงวิธีการอื่นในการตอบกลับที่ดีของ @ Terdonโดยใช้sed:

$ sed '1!G;h;$!d' file | grep -m 1 $pattern
$ sed -n '1!G;h;$p' file | grep -m 1 $pattern

ตัวอย่าง

$ seq 10 > file

$ sed '1!G;h;$!d' file | grep -m 1 5
5

$ sed -n '1!G;h;$p' file | grep -m 1 5
5

ใช้ Perl

เป็นโบนัสนี่เป็นสัญกรณ์ที่ง่ายขึ้นเล็กน้อยใน Perl ที่ต้องจำ:

$ perl -e 'print reverse <>' file | grep -m 1 $pattern

ตัวอย่าง

$ perl -e 'print reverse <>' file | grep -m 1 5
5

1
ว่า (โดยเฉพาะsedอย่างใดอย่างหนึ่ง) มีแนวโน้มที่จะเป็นคำสั่งหลายขนาดช้ากว่าหรือgrep 5 | tail -n1 sed '/5/h;$!d;g'นอกจากนี้ยังอาจใช้หน่วยความจำมาก มันไม่ได้พกพาได้มากขึ้นเมื่อคุณยังใช้ GNU grep -mอยู่
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.