Grep từ cuối tập tin đến đầu


38

Tôi có một tệp có khoảng 30.000.000 dòng (Kế toán bán kính) và tôi cần tìm kết quả khớp cuối cùng của một mẫu nhất định.

Lệnh:

tac accounting.log | grep $pattern

cung cấp những gì tôi cần, nhưng nó quá chậm vì trước tiên HĐH phải đọc toàn bộ tệp và sau đó gửi đến đường ống.

Vì vậy, tôi cần một cái gì đó nhanh chóng có thể đọc tệp từ dòng cuối cùng đến dòng đầu tiên.

Câu trả lời:


44

tacchỉ giúp nếu bạn cũng sử dụng grep -m 1(giả sử GNU grep) grepdừng lại sau trận đấu đầu tiên:

tac accounting.log | grep -m 1 foo

Từ man grep:

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

Trong ví dụ trong câu hỏi của bạn, cả hai tacgrepcần xử lý toàn bộ tệp để sử dụng taclà vô nghĩa.

Vì vậy, trừ khi bạn sử dụng grep -m, hoàn toàn không sử dụng tac, chỉ cần phân tích kết quả đầu ra grepđể có được kết quả cuối cùng:

grep foo accounting.log | tail -n 1 

Một cách tiếp cận khác là sử dụng Perl hoặc bất kỳ ngôn ngữ kịch bản nào khác. Ví dụ: (nơi $pattern=foo):

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

hoặc là

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

1
Tôi đang sử dụng tac vì tôi cần tìm kết quả cuối cùng của một mẫu nhất định. Sử dụng đề xuất "grep -m1" của bạn, thời gian thực hiện chuyển từ 0m0.597s sang 0m0.007s \ o /. Cảm ơn tất cả mọi người!
Hábner Costa 2/214

1
@ HábnerCosta bạn rất hoan nghênh. Tôi hiểu lý do tại sao bạn đang sử dụng tac, quan điểm của tôi là nó không hữu ích trừ khi bạn cũng sử dụng -mvì tệp vẫn cần phải được đọc đầy đủ bởi hai chương trình. Mặt khác, bạn chỉ có thể tìm kiếm tất cả các lần xuất hiện và chỉ giữ lại lần xuất hiện cuối cùng như tôi làm tail -n 1.
terdon

6
Tại sao bạn nói "tac [...] cần xử lý toàn bộ tệp"? Điều đầu tiên tac làm là tìm đến cuối tập tin và đọc một khối từ cuối. Bạn có thể tự xác minh điều này bằng strace (1). Khi kết hợp với grep -mnó, nó sẽ khá hiệu quả.
camh

1
@camh khi kết hợp với grep -mnó là. OP đã không sử dụng -mvì vậy cả hai grep và tac được chế biến toàn bộ sự việc.
terdon

Bạn có thể vui lòng mở rộng về ý nghĩa của awkdòng?
Sopalajo de Arrierez

12

Lý do tại sao

tac file | grep foo | head -n 1

Không dừng lại ở trận đấu đầu tiên là vì bộ đệm.

Thông thường, head -n 1thoát sau khi đọc một dòng. Vì vậy, grepnên lấy SIGPIPE và thoát cũng như ngay khi nó viết dòng thứ hai.

Nhưng điều xảy ra là bởi vì đầu ra của nó không đi đến một thiết bị đầu cuối, nên grepđệm nó. Đó là, nó không viết nó cho đến khi nó tích lũy đủ (4096 byte trong thử nghiệm của tôi với GNU grep).

Điều đó có nghĩa là nó grepsẽ không thoát ra trước khi nó đã ghi 8192 byte dữ liệu, vì vậy có lẽ khá nhiều dòng.

Với GNU grep, bạn có thể làm cho nó thoát ra sớm hơn bằng cách sử dụng --line-bufferednó để ghi nó ngay khi chúng được tìm thấy bất kể có đi đến thiết bị đầu cuối hay không. Vì vậy, grepsau đó sẽ thoát khỏi dòng thứ hai nó tìm thấy.

Nhưng với GNU grepdù sao đi nữa, bạn có thể sử dụng -m 1thay vì như @terdon đã hiển thị, sẽ tốt hơn khi nó thoát ở trận đấu đầu tiên.

Nếu bạn grepkhông phải là GNU grep, thì bạn có thể sử dụng sedhoặc awkthay vào đó. Nhưng tac là một lệnh GNU, tôi nghi ngờ bạn sẽ tìm thấy một hệ thống với tacnơi grepkhông phải là GNU grep.

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

Một số hệ thống phải tail -rlàm điều tương tự như GNU tac.

Lưu ý rằng, đối với các tệp thông thường (có thể tìm kiếm) tactail -rhiệu quả vì chúng đọc các tệp ngược, chúng không chỉ đọc tệp đầy đủ trong bộ nhớ trước khi in ngược (như cách tiếp cận sed của @ slm hoặc tactrên các tệp không thông thường) .

Trên các hệ thống không có sẵn tachoặc không tail -rcó sẵn, các tùy chọn duy nhất là triển khai đọc ngược bằng tay với các ngôn ngữ lập trình như perlhoặc sử dụng:

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

Hoặc là:

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

Nhưng những điều đó có nghĩa là tìm tất cả các trận đấu và chỉ in cái cuối cùng.


4

Đây là một giải pháp khả thi sẽ tìm vị trí xuất hiện mẫu đầu tiên từ cuối:

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

Điều này sử dụng các công tắc -s-rchuyển mạch tacnhư sau:

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

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

Ngoại trừ bạn sẽ mất tất cả mọi thứ nằm giữa điểm bắt đầu của dòng và mẫu.
ychaouche

2

Sử dụng sed

Hiển thị một số phương pháp thay thế cho câu trả lời hay của @ Terdon bằng cách sử dụng sed:

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

Ví dụ

$ 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

Sử dụng Perl

Như một phần thưởng ở đây, một ký hiệu dễ dàng hơn một chút trong Perl cần nhớ:

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

Thí dụ

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

1
Đó (đặc biệt là sedmột) có khả năng chậm hơn vài bậc so với grep 5 | tail -n1hoặc sed '/5/h;$!d;g'. Nó cũng có khả năng sử dụng rất nhiều bộ nhớ. Nó không dễ mang theo hơn vì bạn vẫn đang sử dụng GNU grep -m.
Stéphane Chazelas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.