grep:内存耗尽


42

我正在做一个非常简单的搜索:

grep -R Milledgeville ~/Documents

一段时间后出现此错误:

grep: memory exhausted

如何避免这种情况?

我的系统上有10GB的RAM,运行的应用程序很少,所以我真的很惊讶一个简单的grep内存不足。~/Documents大约100GB,其中包含各种文件。

grep -RI 可能没有这个问题,但是我也想在二进制文件中搜索。

Answers:


46

两个潜在的问题:

  • grep -Rgrep在OS / X 10.8及更高版本上找到经过修改的GNU除外)遵循符号链接,因此,即使其中只有100GB的文件~/Documents,也可能会有符号链接/,例如,您将最终扫描包括文件在内的整个文件系统喜欢/dev/zero。使用grep -r较新的GNU grep,或使用标准的语法:

    find ~/Documents -type f -exec grep Milledgeville /dev/null {} +
    

    (但是请注意,退出状态不会反映该模式是否匹配的事实)。

  • grep查找与模式匹配的线。为此,它必须一次在内存中加载一行。grep与许多其他grep实现相反,GNU 对其读取的行数没有限制,并支持在二进制文件中进行搜索。因此,如果您的文件行很大(也就是说,两个换行符相距很远),大于可用内存,它将失败。

    稀疏文件通常会发生这种情况。您可以使用以下方法重现它:

    truncate -s200G some-file
    grep foo some-file
    

    那一个很难解决。您可以这样做(仍然使用GNU grep):

    find ~/Documents -type f -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} +
    

    它将NUL字符序列转换为一个换行字符,然后再将输入提供给grep。这将涵盖由于文件稀疏而导致问题的情况。

    您可以通过仅对大文件进行优化:

    find ~/Documents -type f \( -size -100M -exec \
      grep -He Milledgeville {} + -o -exec sh -c 'for i do
      tr -s "\0" "\n" < "$i" | grep --label="$i" -He "$0"
      done' Milledgeville {} + \)
    

    如果文件不是稀疏的,并且您在grep之前具有GNU版本2.6,则可以使用该--mmap选项。这些行将被映射到内存中,而不是被复制到内存中,这意味着系统可以始终通过将页面分页到文件来回收内存。该选项已在GNU grep2.6 中删除


实际上,GNU grep并不关心读取一行,而是将文件的很大一部分读取到单个缓冲区中。“此外,GNU grep避免将输入断开。” 来源:lists.freebsd.org/pipermail/freebsd-current/2010-August/…–
Godric Seer

4
@GodricSeer,它仍然可以将文件的很大一部分读入单个缓冲区,但是如果它没有在其中找到字符串并且也没有找到换行符,那么我敢打赌,它将单个缓冲区保留在内存中并读取下一个缓冲区,因为如果找到匹配项,则必须显示该缓冲区。因此,问题仍然相同。实际上,200GB稀疏文件上的grep确实会因OOM而失败。
斯特凡Chazelas

1
@GodricSeer,好吧。如果行都很小,则grep可以丢弃到目前为止已处理的缓冲区。您可以无限期地grep输出,yes而无需使用超过几千字节的内存。问题线的大小。
斯特凡Chazelas

3
GNU grep --null-data选项在这里也可能有用。它强制使用NUL代替换行符作为输入行终止符。
iruvar

1
@ 1_CR,很好,尽管这也会将输出行终止符设置为NUL。
斯特凡Chazelas

5

我通常会

find ~/Documents | xargs grep -ne 'expression'

我尝试了很多方法,发现这是最快的。请注意,这不能很好地处理文件名带有空格的文件。如果您知道这种情况,并且拥有grep的GNU版本,则可以使用:

find ~/Documents -print0 | xargs -0 grep -ne 'expression'

如果没有,您可以使用:

 find ~/Documents -exec grep -ne 'expression' "{}" \;

exec每个文件将使用grep。


这将在带有空格的文件上断开。
克里斯·

嗯,是的。
Kotte 2013年

您可以通过find -print0 | xargs -0 grep -ne 'expression'
Drav Sloan

@ChrisDown而不是便携式解决方案,而不是便携式解决方案。
雷托

@ChrisDown大多数主要的Unix系统都采用了find -print0xargs -0现在:所有三个BSD,MINIX 3,Solaris 11中,...
吉尔“SO-停止作恶”

4

我可以想到一些解决方法:

  • 而不是一次grep所有文件,一次只做一个文件。例:

    find /Documents -type f -exec grep -H Milledgeville "{}" \;
    
  • 如果您只需要知道哪些文件包含单词,则grep -l改为执行。由于grep会在第一次点击后停止搜索,因此不必继续读取任何大文件

  • 如果您确实还想要实际的文本,则可以沿以下两个字符串插入字符串:

    for file in $( grep -Rl Milledgeville /Documents ); do grep -H Milledgeville "$file"; done
    

最后一个示例的语法无效-您需要执行命令替换(并且不应这样做,因为grep输出使用的分隔符在文件名中是合法的)。您还需要引用$file
克里斯·唐尼

后一个示例的问题是文件名中包含换行符或空格(它将导致for将文件作为两个参数来处理)
Drav Sloan 2013年

@DravSloan您的编辑虽然有所改进,但仍会破坏合法文件名。
克里斯·唐纳

1
是的,我把它留了下来,因为它是她回答的一部分,我只是想对其进行改进,以便使其运行(对于文件中没有空格/换行符等的情况)。
Drav Sloan

他的更正->她,我很抱歉,詹妮:/
Drav Sloan 2013年

1

我正在抓紧6TB磁盘以搜索丢失的数据,并使内存耗尽-错误。这也应该适用于其他文件。

我们想出的解决方案是使用dd读取大块磁盘,并grepping大块。这是代码(big-grep.sh):

#problem: grep gives "memory exhausted" error on 6TB disks
#solution: read it on parts
if [ -z $2 ] || ! [ -e $1 ]; then echo "$0 file string|less -S # greps in chunks"; exit; fi

FILE="$1"
MATCH="$2"

SIZE=`ls -l $1|cut -d\  -f5`
CHUNKSIZE=$(( 1024 * 1024 * 1 )) 
CHUNKS=100 # greps in (100 + 1) x 1MB = 101MB chunks
COUNT=$(( $SIZE / $CHUNKSIZE * CHUNKS ))

for I in `seq 0 $COUNT`; do
  dd bs=$CHUNKSIZE skip=$(($I*$CHUNKS)) count=$(( $CHUNKS+1)) if=$FILE status=none|grep -UF -a --context 6 "$MATCH"
done

1
除非您读取重叠的块,否则可能会错过块边界上的匹配项。重叠必须至少与您期望匹配的字符串一样大。
库萨兰达

更新为在每100MB的块中搜索额外的1MB ...便宜的hack
Dagelf
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.