从文件末尾到开头的Grep


38

我有一个大约30.000.000行(半径会计)的文件,我需要找到给定模式的最后一个匹配项。

命令:

tac accounting.log | grep $pattern

给出了我需要的东西,但是它太慢了,因为操作系统必须先读取整个文件,然后再发送到管道。

因此,我需要快速的东西,可以从最后一行读取文件到第一行。

Answers:


44

tac仅在您还使用grep -m 1(假设GNU grepgrep在第一个比赛之后停止时才有帮助:

tac accounting.log | grep -m 1 foo

来自man grep

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

在你的问题,这两个例子tacgrep需要处理整个文件,以便使用tac是一种毫无意义的。

因此,除非您使用,否则grep -m根本不要使用tac,只需解析输出grep以获取最后一个匹配项:

grep foo accounting.log | tail -n 1 

另一种方法是使用Perl或任何其他脚本语言。例如(where $pattern=foo):

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

要么

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

1
我使用tac是因为我需要找到给定模式的最后一个匹配项。使用您的建议“ grep -m1”,执行时间从0m0.597s变为0m0.007s \ o /。谢谢大家!
哈伯纳·科斯塔2014年

1
@HábnerCosta非常欢迎。我理解您为什么使用tac,我的意思是除非您也使用它,否则它无济于事,-m因为该文件仍需要两个程序完整读取。否则,您可以像我一样搜索所有发生的事件,并仅保留最后一个tail -n 1
terdon

6
为什么说“ TAC需要处理整个文件”?tac要做的第一件事是查找文件的末尾并从末尾读取一个块。您可以使用strace(1)自己验证。与结合使用时grep -m,它应该非常有效。
camh 2014年

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缓冲。也就是说,直到它积累了足够的内存(在我使用GNU grep的测试中为4096字节)之前,它才开始编写它。

这意味着grep在写入8192字节数据之前不会退出,因此可能需要很多行。

使用GNU grep,您可以使用--line-buffered来告诉它只要找到行就立即写线,而不管是否去终端,可以使它更快退出。因此,grep将在找到的第二行退出。

但是grep无论如何,对于GNU ,您可以使用-m 1@terdon所示的方法,因为它在第一个匹配项时退出,效果更好。

如果您grep不是GNU grep,则可以使用sedawk代替。但是tac ,作为GNU命令,我怀疑您会找到一个系统,tac其中where grep不是GNU grep

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
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.