递归grep vs find / -type f -exec grep {} \; 哪个更有效/更快?


70

在整个文件系统中查找包含字符串的文件中,哪个更有效:递归grep或在exec语句中使用grep查找?我认为find会更有效,因为如果您知道文件扩展名或与文件名匹配的正则表达式,则至少可以进行一些过滤,但是当您仅知道-type f哪个更好时?GNU grep 2.6.3; 查找(GNU findutils)4.4.2

例:

grep -r -i 'the brown dog' /

find / -type f -exec grep -i 'the brown dog' {} \;


1
数学/计算机科学/算法效率并非基于观点。
2014年

检查这一。尽管不是递归的,但可以更好地理解。unix.stackexchange.com/questions/47983/…–
拉梅什

8
@AvinashRaj他没有要求意见。他问哪个更有效和/或更快捷,而不是哪个“更好”。这是一个完全可以回答的问题,它有一个特定的答案,具体答案取决于这两个程序的工作方式以及您给他们提供的确切搜索内容。
terdon

2
请注意,-exec {} +表单将执行较少的派生,因此应比更快-exec {} \;。您可能需要将-H(或-h)添加到grep选项中以获得完全等效的输出。
Mikel 2014年

您可能不希望第二个-r选项打开grep
qwertzguy 2015年

Answers:


85

我不确定:

grep -r -i 'the brown dog' /*

真的是你的意思 这意味着grep会在所有非隐藏文件和目录中递归/(但仍在隐藏文件和目录中查找)。

假设您的意思是:

grep -r -i 'the brown dog' /

注意事项:

  • 并非所有的grep实现都支持-r。在执行这些操作的行为之间,行为也有所不同:在遍历目录树时,某些行为遵循目录的符号链接(这意味着您可能最终在同一文件中查找了几次,甚至在无限循环中运行),而某些行为则没有。有些会查看设备文件(例如,这需要花费一些时间/dev/zero)或管道或二进制文件的内部...,有些则不会。
  • grep发现文件后立即开始查找文件是非常有效的。但是,尽管它在文件中查找,但不再寻找要搜索的文件(在大多数情况下,它也是如此)

你的:

find / -type f -exec grep -i 'the brown dog' {} \;

(删除了-r此处没有意义的文件)效率非常低,因为grep每个文件只运行一个文件。;仅应用于仅接受一个参数的命令。而且在这里,由于grep只查找一个文件,因此不会打印文件名,因此您将不知道匹配项在哪里。

您没有在查看设备文件,管道,符号链接...,也没有在关注符号链接,但仍可能在诸如之类的内容内部/proc/mem

find / -type f -exec grep -i 'the brown dog' {} +

会好很多,因为grep将运行尽可能少的命令。除非上一次运行只有一个文件,否则您将获得文件名。为此,最好使用:

find / -type f -exec grep -i 'the brown dog' /dev/null {} +

或使用GNU grep

find / -type f -exec grep -Hi 'the brown dog' {} +

请注意,只有找到足够的文件以供其咀嚼grep之前find,它才会启动,因此会有一些初始延迟。并且find将不会继续搜索更多文件,直到先前的文件grep返回为止。分配和传递大文件列表会产生一些(可能微不足道的)影响,因此,总的来说,它的效率可能会低于grep -r不遵循符号链接或查看设备内部信息的效率。

使用GNU工具:

find / -type f -print0 | xargs -r0 grep -Hi 'the brown dog'

如上所述,grep将运行尽可能少的实例,但find在第一次grep调用在第一批内部时将继续查找更多文件。不过,这可能是优势,也可能不是优势。例如,将数据存储在旋转硬盘驱动器上,find以及grep访问存储在磁盘上不同位置的数据将导致磁盘磁头不断移动,从而减慢磁盘吞吐量。在RAID设置(其中find,并grep可以访问不同的磁盘上),或者放在SSD上,可能使一个积极的变化。

在RAID设置中,运行多个并发 grep调用也可能会有所改善。仍然在具有3个磁盘的RAID1存储上使用GNU工具,

find / -type f -print0 | xargs -r0 -P2 grep -Hi 'the brown dog'

可能会大大提高性能。但是请注意,grep只有找到足够的文件来填充第一个grep命令后,才会启动第二个命令。您可以为它添加一个-n选项xargs,以使它更快地发生(并且每次grep调用传递更少的文件)。

还要注意,如果将xargs输出重定向到除终端设备以外的任何设备,则grepss将开始缓冲其输出,这意味着这些greps 的输出可能会被错误地交错。您必须在它们上使用stdbuf -oL(如在GNU或FreeBSD上可用)来解决该问题(您可能仍然会遇到很长的行(通常> 4KiB)的问题)或将它们的输出写在单独的文件中并进行连接最后。

在这里,您要查找的字符串是固定的(不是正则表达式),因此使用该-F选项可能会有所作为(不太可能因为grep实现已经知道如何对其进行优化)。

可能会有很大不同的另一件事是,如果您处于多字节语言环境,则将语言环境固定为C:

find / -type f -print0 | LC_ALL=C xargs -r0 -P2 grep -Hi 'the brown dog'

为了避免看里面/proc/sys...,使用-xdev并指定要在搜索的文件系统:

LC_ALL=C find / /home -xdev -type f -exec grep -i 'the brown dog' /dev/null {} +

或修剪要明确排除的路径:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +

我不认为有人可以指出我的资源-或解释-{}和+的含义。我在exec,grep手册页中看不到任何内容,也没有在我使用的Solaris框上找到任何内容。仅仅是shell连接文件名并将它们传递给grep吗?

3
@Poldie,这显然在的说明中解释-exec谓语在Solaris手册页
斯特凡Chazelas

是啊。在手册页中搜索时,我并没有转义{char。您的链接更好;我发现手册页很难读。

1
带3个磁盘的RAID1?多么奇怪...
修补

1
@tink,是的,RAID1在2个或更多磁盘上。使用3个磁盘而不是2个磁盘,可以提高冗余度和读取性能,而写入性能大致相同。使用3个磁盘而不是2个磁盘,这意味着您还可以更正错误,例如当其中一个副本出现一点翻转时,通过检查2个磁盘中的所有3个副本,您可以分辨出哪一个是正确的真的告诉。
斯特凡Chazelas

13

如果*grep呼叫不是对你很重要,则首先应该是因为只有一个实例更有效的grep启动,和叉子不是免费的。在大多数情况下,即使使用,它也会更快,*但在边缘情况下,排序可能会逆转。

可能还有其他的find- grep这与很多小文件的效果特别的结构。一次读取大量文件条目和索引节点可能会提高旋转媒体的性能。

但是,让我们看一下syscall统计信息:

> strace -cf find . -type f -exec grep -i -r 'the brown dog' {} \;
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.86    0.883000        3619       244           wait4
  0.53    0.004809           1      9318      4658 open
  0.46    0.004165           1      6875           mmap
  0.28    0.002555           3       977       732 execve
  0.19    0.001677           2       980       735 stat
  0.15    0.001366           1      1966           mprotect
  0.09    0.000837           0      1820           read
  0.09    0.000784           0      5647           close
  0.07    0.000604           0      5215           fstat
  0.06    0.000537           1       493           munmap
  0.05    0.000465           2       244           clone
  0.04    0.000356           1       245       245 access
  0.03    0.000287           2       134           newfstatat
  0.03    0.000235           1       312           openat
  0.02    0.000193           0       743           brk
  0.01    0.000082           0       245           arch_prctl
  0.01    0.000050           0       134           getdents
  0.00    0.000045           0       245           futex
  0.00    0.000041           0       491           rt_sigaction
  0.00    0.000041           0       246           getrlimit
  0.00    0.000040           0       489       244 ioctl
  0.00    0.000038           0       591           fcntl
  0.00    0.000028           0       204       188 lseek
  0.00    0.000024           0       489           set_robust_list
  0.00    0.000013           0       245           rt_sigprocmask
  0.00    0.000012           0       245           set_tid_address
  0.00    0.000000           0         1           uname
  0.00    0.000000           0       245           fchdir
  0.00    0.000000           0         2         1 statfs
------ ----------- ----------- --------- --------- ----------------
100.00    0.902284                 39085      6803 total

仅grep

> strace -cf grep -r -i 'the brown dog' .
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 40.00    0.000304           2       134           getdents
 31.71    0.000241           0       533           read
 18.82    0.000143           0       319         6 openat
  4.08    0.000031           4         8           mprotect
  3.29    0.000025           0       199       193 lseek
  2.11    0.000016           0       401           close
  0.00    0.000000           0        38        19 open
  0.00    0.000000           0         6         3 stat
  0.00    0.000000           0       333           fstat
  0.00    0.000000           0        32           mmap
  0.00    0.000000           0         4           munmap
  0.00    0.000000           0         6           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0       245       244 ioctl
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0       471           fcntl
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           futex
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0       132           newfstatat
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.000760                  2871       466 total

1
在搜索整个文件系统的规模上,派生可以忽略不计。I / O是您要减少的。
吉尔斯2014年

尽管这是OP的错误,但比较是不正确的,但您应在使用时删除的-r标记。通过比较发生的次数,您可以看到它反复搜索了相同的文件。grepfindopen
qwertzguy 2015年

1
@qwertzguy,不,由于-r应该-type f保证所有参数都不是目录,因此应当无害。多重open()s为更可能下降到通过打开其他文件grep在每次调用(库,本地化数据...)(感谢我的答案编辑BTW)
斯特凡Chazelas

5

如果您使用的是SSD且查找时间可以忽略不计,则可以使用GNU并行:

find /path -type f | parallel --gnu --workdir "$PWD" -j 8 '
    grep -i -r 'the brown dog' {} 
'

根据找到的内容,这将同时执行多达8个grep进程find

这将损坏硬盘驱动器,但SSD应该可以很好地应对。


-1

关于这一点要考虑的另一件事如下。

grep将必须递归通过的目录中是否会包含比系统的nofile设置更多的文件?(例如,打开文件句柄的数量,在大多数Linux发行版中默认为1024)

如果是这样,则查找绝对是正确的方法,因为当某些版本的grep命中的文件目录超过最大打开文件句柄设置的目录数时,它会发出一个参数列表过长的错误。

只是我的2英镑。


1
为什么会grep炸出来?至少在GNU grep中,如果您给出带有尾随的路径/并使用-R它,它将简单地遍历目录。该外壳是不会,除非你给壳水珠扩大东西。因此,在给定的示例(/*)中,仅/物质的内容,而不是子文件夹的内容,子文件夹将简单地由枚举grep,而不作为参数从外壳传递。
0xC0000022L 2014年

好吧,考虑到OP正在询问递归搜索(例如“ grep -r -i'棕狗'/ *”),我已经看到GNU的grep(至少是2.9版)炸弹爆炸了:“-bash:/ bin / grep:参数列表太长”,使用精确搜索在其中包含140,000个子目录的目录上使用的OP。
B.Kaatz '16
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.