如何grep大量的文件?

时间:2014-05-09 19:54:09

标签: bash grep

我正在尝试当前目录中的grep 40k文件,我收到此错误。

for i in $(cat A01/genes.txt); do grep $i *.kaks; done > A01/A01.result.txt
-bash: /usr/bin/grep: Argument list too long

一个人通常grep个数千个文件?

由于 众议员

5 个答案:

答案 0 :(得分:29)

这让大卫伤心......

到目前为止,每个人都错了(anubhava除外)。

Shell脚本与任何其他编程语言都不同,因为对行的大部分解释来自于在实际执行命令之前插入它们的shell的强大功能。

让我们采取简单的措施:

$ set -x
$ ls
+ ls
bar.txt foo.txt fubar.log
$ echo The text files are *.txt
echo The text files are *.txt
> echo The text files are bar.txt foo.txt
The text files are bar.txt foo.txt
$ set +x
$

set -x允许您查看shell实际如何插入glob,然后将其作为输入传递回命令。 >指向命令实际执行的行。

您可以看到echo命令无法解释*。相反,shell会抓取*并将其替换为匹配文件的名称。然后,只有echo命令实际执行命令。

如果您有40K以上的文件,并且grep *,那么您*之前将grep扩展为这些40,000多个文件的名称,甚至有机会执行,以及错误消息 / usr / bin / grep:参数列表太长的来源。

幸运的是,Unix可以解决这个难题:

$ find . -name "*.kaks" -type f -maxdepth 1 | xargs grep -f A01/genes.txt

find . -name "*.kaks" -type f -maxdepth 1会找到所有*.kaks个文件,而-depth 1只会包含当前目录中的文件。 -type f确保您只选择文件而不是目录。

find命令将文件名称管道为xargsxargs会将文件名称附加到grep -f A01/genes.txt命令。但是,xargs有一个技巧。它知道命令行缓冲区的长度,并在命令行缓冲区已满时执行grep,然后将另一系列文件传递给grep。这样,grep可能执行三次或十次(取决于命令行缓冲区的大小),并且我们使用了所有文件。

不幸的是,xargs使用空格作为文件名的分隔符。如果您的文件包含空格或标签,则您xargs会遇到问题。幸运的是,还有另一个问题:

$ find . -name "*.kaks" -type f -maxdepth 1 -print0 | xargs -0 grep -f A01/genes.txt

-print0将导致find打印出不是由换行符分隔的文件名,而是打印出NUL字符。 -0的{​​{1}}参数告诉xargs文件分隔符不是空格,而是NUL字符。因此,解决了这个问题。

您也可以这样做:

xargs

这将为找到的每个文件而不是$ find . -name "*.kaks" -type f -maxdepth 1 -exec grep -f A01/genes.txt {} \; 执行grep,并且只对命令行上可以填充的所有文件运行xargs。这样做的好处是它完全避免了外壳干扰。但是,效率可能会降低,也可能不会降低效率。

有趣的是尝试并查看哪一个更有效。您可以使用grep查看:

time

这将执行命令,然后告诉你需要多长时间。使用$ time find . -name "*.kaks" -type f -maxdepth 1 -exec grep -f A01/genes.txt {} \; -exec进行尝试,看看哪个更快。让我们知道你发现了什么。

答案 1 :(得分:7)

您可以将findgrep合并为:

find . -maxdepth 1 -name '*.kaks' -exec grep -H -f A01/genes.txt '{}' \; > A01/A01.result.txt

答案 2 :(得分:0)

您可以使用grep的递归功能:

for i in $(cat A01/genes.txt); do 
    grep -r $i .
done > A01/A01.result.txt

但是如果您只想选择kaks个文件:

for i in $(cat A01/genes.txt); do 
    find . -iregex '.*\.kaks$' -exec grep $i \;
done > A01/A01.result.txt

答案 3 :(得分:0)

在你的外部循环中放入另一个循环:

for f in *.kaks; do
   grep -H  $i "$f"
done

顺便说一下,您是否有兴趣在每个文件中找到每个事件,或者仅仅搜索字符串存在一次或多次?如果它足够好"要知道字符串出现在那里一次或多次你可以指定" -n 1" grep并且在找到第一个匹配后不会费心读取/搜索文件的其余部分,这可能会节省大量时间。

答案 4 :(得分:0)

以下解决方案对我有效:

问题:

 grep -r "example\.com" *
 -bash: /bin/grep: Argument list too long

解决方案:

grep -r "example\.com" .

[“在较新版本的grep中,您可以省略“。”,因为它暗示了当前目录。”]

来源: 赖因里克(J. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-6