我正在尝试在当前目录下使用grep
查找4万个文件,但是我收到了这个错误提示。
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
搜索数千个文件?
谢谢 Upendra
这让David很难过...
到目前为止,每个人都错了(除了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实际插值的通配符,并将其作为输入传递给命令。 >
指向实际被命令执行的行。
你可以看到echo
命令没有解释*
。相反,shell获取了*
并用匹配文件的名称替换它。只有在这之后,echo
命令才会真正执行该命令。
当你有超过40K个文件时,并且你执行grep *
命令时,在grep
有机会执行之前,你已经将该*
扩展为这40,000多个文件的名称,这就是错误消息/usr/bin/grep: Argument list too long的来自之处。
幸运的是,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
命令将文件名传递给 xargs
,xargs
将文件名追加到 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字符分隔。对于xargs
命令,-0
参数告诉xargs
文件分隔符不是空格,而是NUL字符。因此,问题得以解决。
你也可以这样做:
$ find . -name "*.kaks" -type f -maxdepth 1 -exec grep -f A01/genes.txt {} \;
这将执行每个找到的文件的 grep
,而不是像 xargs
那样仅对可以放在命令行上运行的所有文件运行 grep
。这样做的好处是完全避免了 shell 的干扰。但是,它可能会更或者更少有效。
有趣的是进行实验并查看哪种方法更有效。您可以使用 time
查看:
$ time find . -name "*.kaks" -type f -maxdepth 1 -exec grep -f A01/genes.txt {} \;
这将执行命令,然后告诉您执行的时间。请尝试使用-exec
和xargs
,并查看哪个更快。让我们知道您的发现。
-exec command {} +
形式,不是吗? - John1024find
命令中 +
的用法。我以前从没见过这种写法。是的,根据 man 手册:与 -exec 相同,但每次调用实用程序时,“{}”将被尽可能多的路径名替换。此行为类似于 xargs(1)。。这就是当你学习20年前的东西,而没有跟上变化的结果。 - David W.find
和grep
:find . -maxdepth 1 -name '*.kaks' -exec grep -H -f A01/genes.txt '{}' \; > A01/A01.result.txt
cat A01/genes.txt
,并且避免了(b)在命令行上扩展*.kaks
。 - John1024你可以使用 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
for f in *.kaks; do
grep -H $i "$f"
done
问题:
grep -r "example\.com" *
-bash: /bin/grep: Argument list too long
解决方案:
grep -r "example\.com" .
["在较新版本的grep中,您可以省略“.”,因为当前目录是隐含的。"]
来源: Reinlick, J. https://www.saotn.org/bash-grep-through-large-number-files-argument-list-too-long/
find
。 - keyser