我有约3万个文件。每个文件包含约10万行。一行不包含空格。单个文件中的行已排序并免费重复。
我的目标:我想找到两个或多个文件中所有重复的行,以及包含重复条目的文件的名称。
一个简单的解决方案是:
cat *.words | sort | uniq -c | grep -v -F '1 '
然后我会运行:
grep 'duplicated entry' *.words
您看到更有效的方法了吗?
我有约3万个文件。每个文件包含约10万行。一行不包含空格。单个文件中的行已排序并免费重复。
我的目标:我想找到两个或多个文件中所有重复的行,以及包含重复条目的文件的名称。
一个简单的解决方案是:
cat *.words | sort | uniq -c | grep -v -F '1 '
然后我会运行:
grep 'duplicated entry' *.words
您看到更有效的方法了吗?
Answers:
由于所有输入文件均已排序,因此我们可以绕过实际的排序步骤,而仅sort -m
用于将文件合并在一起。
在某些Unix系统上(据我所知仅 Linux),这可能足以完成
sort -m *.words | uniq -d >dupes.txt
将重复的行写入文件dupes.txt
。
要查找这些行来自什么文件,您可以执行以下操作
grep -Fx -f dupes.txt *.words
这将指示grep
将dupes.txt
(-f dupes.txt
)中的行视为固定字符串模式(-F
)。grep
还将要求整行从头到尾完全匹配(-x
)。它将文件名和行打印到终端。
在某些Unix系统上,30000个文件名将扩展为一个字符串,该字符串太长而无法传递给单个实用程序(这sort -m *.words
将导致失败Argument list too long
,它在OpenBSD系统上会失败)。如果文件数量大得多,甚至Linux都会抱怨这一点。
这意味着,在一般情况下(这也将与工作很多不止30000文件),一个具有“块”的排序:
rm -f tmpfile
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
或者,tmpfile
不创建以下内容xargs
:
rm -f tmpfile
find . -type f -name '*.words' -exec sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh {} +
这将在当前目录(或以下)中找到名称匹配的所有文件*.words
。对于一次适当大小的这些名称块,其大小由xargs
/ 确定find
,它将它们合并在一起成为排序的tmpfile
文件。如果tmpfile
已经存在(除了第一个块以外的所有块),该文件还将与当前块中的其他文件合并。根据文件名的长度以及命令行的最大允许长度,这可能需要内部脚本运行10次以上(find
或xargs
自动运行)。
“内部” sh
脚本,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
用于sort -o tmpfile
输出到tmpfile
(tmpfile
即使这也是的输入也不会覆盖sort
)并-m
进行合并。在这两个分支中,"$@"
将扩展为从find
或传递到脚本的单独引用文件名的列表xargs
。
然后,只需运行uniq -d
上tmpfile
得到是重复的所有行:
uniq -d tmpfile >dupes.txt
如果您喜欢“ DRY”原则(“不要重复自己”),则可以将内部脚本编写为
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
要么
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
出于与上述相同的原因,我们无法使用grep -Fx -f dupes.txt *.words
这些重复项的来源,因此我们find
再次使用:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
由于没有要执行的“复杂”处理,因此我们可以grep
直接从调用-exec
。该-exec
选项接受一个实用程序命令,并将找到的名称放在中{}
。随着+
末,find
将放置在发生的许多论点{}
作为实用程序的每个调用当前shell支持。
是完全正确的,一个可能希望用
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
要么
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
确保文件名始终包含在的输出中grep
。
第一个变体用于grep -H
始终输出匹配的文件名。最后一个变种使用的事实是,grep
如果在命令行上给出多个文件,则将包括匹配文件的名称。
这很重要,因为grep
从中发送的最后一部分文件名find
实际上可能只包含一个文件名,在这种情况下grep
,结果中不会提及该文件名。
奖励材料:
find
+ xargs
+ sh
命令:find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
find . -type f -name '*.words'
会从当前目录(或以下)简单地生成一个路径名列表,其中每个路径名都是常规文件(-type f
)的路径名,并且末尾具有与匹配的文件名部分*.words
。如果仅要搜索当前目录,则可以-maxdepth 1
在.
之前,之后添加一个-type f
。
-print0
将确保以\0
(nul
)字符作为分隔符输出所有找到的路径名。这是一个在Unix路径中无效的字符,它使我们能够处理路径名,即使它们包含换行符(或其他奇怪的东西)也是如此。
find
将其输出传递到xargs
。
xargs -0
将读取\0
路径名称的- 分隔列表,并将使用其中的大块重复执行给定的实用程序,确保使用足够多的参数执行该实用程序,以免外壳程序抱怨过长的参数列表,直到没有更多输入为止来自find
。
通过调用该实用程序xargs
是sh
在命令行作为使用其一个串上给定的脚本-c
标志。
在sh -c '...some script...'
后面跟随参数调用时,参数将可用于中的脚本$@
,但第一个参数除外,该参数将放置在其中$0
(这是您可能会发现的“命令名称”,例如,top
如果您足够快的话)。这就是为什么我们sh
在实际脚本的末尾插入字符串作为第一个参数的原因。字符串sh
是一个伪参数,可以是任何单个单词(有些似乎更喜欢_
或sh-find
)。
fi
是if
“内部” sh
shell脚本中语句的结尾。'
shell脚本的结尾(整个脚本是一个单引号的字符串)。在sh
将被传递给在内部脚本$0
(不是部分$@
,其中将包含文件名)。在这种情况下,该sh
字符串实际上可以是任何单词。如果sh
最后遗漏,则第一个文件名将被传入,$0
并且不会成为内部shell脚本正在执行的处理的一部分。
单个文件中的行已排序并免费重复。
这意味着您可能会发现以下用途sort -m
:
-m, --merge
merge already sorted files; do not sort
另一个明显的替代方法是将awk
数组中的行收集起来并计数。但是,正如@ dave_thompson_085所评论的那样,这30亿行(或者有很多独特的行)可能会占用相当多的内存,因此可能无法很好地工作。
使用awk,您可以通过一个短命令获得所有文件中所有重复的行:
$ awk '_[$0]++' *.words
但是,如果一条线存在3次或更多次,它将重复行。
有一种解决方案仅获取第一个重复项:
$ awk '_[$0]++==1' *.words
它应该很快(如果重复次数很少),但是会吃掉很多内存以将所有行保留在内存中。也许,根据您的实际文件和重复文件,请先尝试3个或4个文件。
$ awk '_[$0]++==1' [123]*.words
否则,您可以执行以下操作:
$ sort -m *.words | uniq -d
它将打印uniq重复行。
sort -m * | uniq -d
'x[$0]++==1'
但是确实需要很多内存;如果3G行有1G不同的值,并且如果awk需要说50个字节的哈希数组条目将一个(可能是短的)字符串映射到uninit值,则为50GB。对于已排序的输入,您可以uniq -d
手动执行,awk '$0==p&&n++==1;$0!=p{p=$0;n=1}'
但是为什么要打扰呢?
==1
,很棒的主意。
awk
存储2.4E11字节(223 GiB)。
sort -m *.words | uniq -d
很棒!完成该过程后,我运行grep
查找包含重复条目的文件。您是否看到一种打印至少一个包含重复条目的文件名的方法?
fi' sh
什么?