在一个文件中查找不在另一个文件中的行的快速方法?
Posted
技术标签:
【中文标题】在一个文件中查找不在另一个文件中的行的快速方法?【英文标题】:Fast way of finding lines in one file that are not in another? 【发布时间】:2013-08-14 19:43:38 【问题描述】:我有两个大文件(文件名集)。每个文件大约有 30.000 行。我正在尝试找到一种快速的方法来查找 file1 中不存在于 file2 中的行。
例如,如果这是 file1:
line1
line2
line3
这是 file2:
line1
line4
line5
那么我的结果/输出应该是:
line2
line3
这行得通:
grep -v -f file2 file1
但是在我的大文件上使用时非常非常慢。
我怀疑有一个使用 diff()
的好方法,但输出应该是 行,没有别的,而且我似乎找不到开关。 p>
谁能帮我找到一种快速的方法,使用 bash 和基本的 Linux 二进制文件?
编辑:要跟进我自己的问题,这是迄今为止我使用diff()
找到的最佳方法:
diff file2 file1 | grep '^>' | sed 's/^>\ //'
当然,一定有更好的方法吗?
【问题讨论】:
如果速度更快,你可以试试这个:awk 'NR==FNRa[$0];next!($0 in a)' file2 file1 > out.txt
无快速要求:***.com/questions/4366533/…
感谢您介绍 grep -v -f file2 file1
参见:Fastest way to find lines of a file from another larger file in Bash。
简化工具集的简单方法:cat file1 file2 file2 | sort | uniq --unique
,请参阅下面的答案。
【参考方案1】:
comm 命令(“common”的缩写)可能很有用 comm - compare two sorted files line by line
#find lines only in file1
comm -23 file1 file2
#find lines only in file2
comm -13 file1 file2
#find lines common to both files
comm -12 file1 file2
man
文件实际上是非常可读的。
【讨论】:
在 OSX 上完美运行。 或许应该突出显示排序输入的要求。comm
也有一个选项来验证输入是否已排序,--check-order
(它似乎无论如何都会这样做,但这个选项会导致它出错而不是继续)。但是要对文件进行排序,只需执行:com -23 <(sort file1) <(sort file2)
等等
我将在 Windows 中生成的文件与在 Linux 中生成的文件进行比较,似乎 comm
根本不起作用。我花了一段时间才弄清楚这是关于行尾的:即使看起来相同的行如果有不同的行尾,也会被认为是不同的。命令dos2unix
可用于将 CRLF 行尾转换为仅 LF。
警告“这不适用于具有 DOS 行结尾的文件”必须或多或少地添加到每个 shell 脚本答案中。这是一个常见的常见问题解答;见***.com/questions/39527571/…【参考方案2】:
您可以通过控制 GNU diff
输出中旧/新/未更改行的格式来实现这一点:
diff --new-line-format="" --unchanged-line-format="" file1 file2
输入文件应该被排序,这样才能正常工作。使用bash
(和zsh
),您可以使用进程替换<( )
就地排序:
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
在上面的 new 和 unchanged 行被抑制,所以只有 changed (即在您的情况下删除的行)被输出。您还可以使用一些其他解决方案不提供的diff
选项,例如-i
来忽略大小写,或者使用各种空格选项(-E
、-b
、-v
等)来实现不太严格的匹配。
说明
--new-line-format
、--old-line-format
和--unchanged-line-format
选项让您控制diff
格式化差异的方式,类似于printf
格式说明符。这些选项分别格式化 new(添加)、old(删除)和 unchanged 行。将一个设置为空 "" 会阻止输出那种行。
如果您熟悉 unified diff 格式,您可以部分地重新创建它:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
%L
说明符是有问题的行,我们用“+”“-”或“”作为前缀,例如 diff -u
(请注意,它只输出差异,它缺少每个分组更改顶部的 ---
+++
和 @@
行)。
您还可以使用它来做其他有用的事情,例如 number each line 和 %dn
。
diff
方法(连同其他建议 comm
和 join
)仅产生带有 sorted 输入的预期输出,但您可以使用 <(sort ...)
就地排序。这是一个简单的awk
(nawk) 脚本(受 Konsolebox 答案中链接到的脚本的启发),它接受任意排序的输入文件, 并且 按照它们在 file1 中出现的顺序输出缺失的行。
# output lines in file1 that are not in file2
BEGIN FS="" # preserve whitespace
(NR==FNR) ll1[FNR]=$0; nl1=FNR; # file1, index by lineno
(NR!=FNR) ss2[$0]++; # file2, index by string
END
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
这会将file1的全部内容逐行存储在行号索引数组ll1[]
中,并将file2的全部内容逐行存储在行内容索引关联数组ss2[]
中。读取两个文件后,遍历ll1
并使用in
运算符确定file1 中的行是否存在于file2 中。 (如果有重复,这将对diff
方法有不同的输出。)
如果文件足够大以至于存储它们都会导致内存问题,您可以通过仅存储 file1 并在读取 file2 的过程中删除匹配项来以 CPU 换取内存。
BEGIN FS=""
(NR==FNR) # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
(NR!=FNR) # file2
if ($0 in ss1) delete ll1[ss1[$0]]; delete ss1[$0];
END
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
上面将file1的全部内容存储在两个数组中,一个以行号ll1[]
为索引,一个以行内容ss1[]
为索引。然后在读取 file2 时,从ll1[]
和ss1[]
中删除每个匹配的行。最后输出 file1 中的剩余行,保持原始顺序。
在这种情况下,对于上述问题,您还可以分而治之,使用 GNU split
(过滤是 GNU 扩展),重复运行文件 1 块并每次完全读取文件 2时间:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
注意-
的使用和位置,意思是stdin
在gawk
命令行上。这是由来自 file1 的 split
以每次调用 20000 行的块提供的。
对于非 GNU 系统上的用户,几乎可以肯定,您可以获得一个 GNU coreutils 软件包,包括在 OSX 上作为 Apple Xcode 工具的一部分,它提供 GNU diff
、awk
,尽管只有一个 POSIX/ BSD split
而不是 GNU 版本。
【讨论】:
这正是我所需要的,只需要巨大的 grep 所花费的时间的一小部分。谢谢! 找到这个gnu manpage 我们中的一些人不在 gnu [OS X bsd here...] :) 我假设您的意思是diff
:通常输入文件会有所不同,在这种情况下,diff
返回 1。认为这是一个奖励 ;-) 如果您在 shell 脚本中进行测试,0 和 1 是预期的退出代码,2 表示有问题。
@mr.spuratic 啊,是的,现在我在man diff
中找到了它。谢谢!【参考方案3】:
就像 konsolebox 建议的那样,海报的 grep 解决方案
grep -v -f file2 file1
如果您简单地添加 -F
选项,将模式视为固定字符串而不是正则表达式,实际上效果很好(更快)。我在一对我必须比较的 ~1000 行文件列表上验证了这一点。在将 grep 输出重定向到 wc -l
时,使用 -F
需要 0.031 秒(实数),而没有它需要 2.278 秒(实数)。
这些测试还包括-x
开关,这是解决方案的必要部分,以确保在 file2 包含与 file1 中的一个或多个行的一部分但不是全部匹配的行的情况下完全准确。
因此,不需要对输入进行排序、快速、灵活(区分大小写等)的解决方案是:
grep -F -x -v -f file2 file1
这不适用于所有版本的 grep,例如它在 macOS 中失败,其中文件 1 中的一行将显示为文件 2 中不存在,即使它与另一行匹配它的子串。或者,您可以install GNU grep on macOS 以使用此解决方案。
【讨论】:
是的,它可以工作,但即使使用-F
这也不能很好地扩展。
这不是那么快,我等了 5 分钟才放弃了 2 个约 500k 行的文件
其实这种方式还是比comm方式慢,因为这种方式可以处理未排序的文件,所以被unsorting拖了下来,comm利用了排序的优势
@workplaylifecycle 您需要添加排序时间,这可能是非常大的file2
的瓶颈。
但是,带有-x
选项的grep 显然会占用更多内存。 file2
包含 6-10 字节的 180M 字,我的进程在 32GB RAM 机器上得到了 Killed
...【参考方案4】:
如果您缺少“花哨的工具”,例如在一些最小的 Linux 发行版中,有一个只有 cat
、sort
和 uniq
的解决方案:
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique
测试:
seq 1 1 7 | sort --random-sort > includes.txt
seq 3 1 9 | sort --random-sort > excludes.txt
cat includes.txt excludes.txt excludes.txt | sort | uniq --unique
# Output:
1
2
与grep
相比,这也相对快。
【讨论】:
注意——某些实现将无法识别--unique
选项。您应该可以为此使用standardized POSIX option:| uniq -u
在示例中,“2”是从哪里来的?
@Niels2000, seq 1 1 7
从 1 创建数字,增量为 1,直到 7,即 1 2 3 4 5 6 7。然后就是你的 2!【参考方案5】:
使用来自moreutils
包的combine
,这是一个支持not
、and
、or
、xor
操作的集合实用程序
combine file1 not file2
即给我在 file1 但不在 file2 中的行
或者给我 file1 中的行减去 file2 中的行
注意: combine
在执行任何操作之前会在两个文件中排序并查找唯一行,但 diff
不会。因此,您可能会发现diff
和combine
的输出之间存在差异。
所以实际上你是在说
在 file1 和 file2 中找到不同的行,然后给我 file1 中的行减去 file2 中的行
根据我的经验,它比其他选项快得多
【讨论】:
【参考方案6】:sort 和 diff 的速度是多少?
sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
【讨论】:
感谢您提醒我在进行 diff 之前需要对文件进行排序。 sort + diff 更快。 一个衬里 ;-) diff 【参考方案7】:$ join -v 1 -t '' file1 file2
line2
line3
-t
确保它比较整行,如果您在某些行中有空格。
【讨论】:
像comm
,join
要求在您正在执行连接操作的字段上对两个输入行进行排序。【参考方案8】:
你可以使用 Python:
python -c '
lines_to_remove = set()
with open("file2", "r") as f:
for line in f.readlines():
lines_to_remove.add(line.strip())
with open("f1", "r") as f:
for line in f.readlines():
if line.strip() not in lines_to_remove:
print(line.strip())
'
【讨论】:
【参考方案9】:这对我来说似乎很快:
comm -1 -3 <(sort file1.txt) <(sort file2.txt) > output.txt
【讨论】:
太棒了,但目标问题只是comm file1 file2
,因为看起来像提供的排序列表【参考方案10】:
使用 fgrep 或向 grep 添加 -F 选项可能会有所帮助。但是为了更快的计算,您可以使用 Awk。
您可以尝试以下 Awk 方法之一:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
【讨论】:
+1 这是唯一不需要对输入进行排序的答案。虽然 OP 显然对这个要求很满意,但在许多现实世界的场景中这是一个不可接受的约束。【参考方案11】:我通常这样做的方式是使用--suppress-common-lines
标志,但请注意,这只有在您以并排格式执行时才有效。
diff -y --suppress-common-lines file1.txt file2.txt
【讨论】:
【参考方案12】:我发现使用普通的 if 和 for 循环语句对我来说效果很好。
for i in $(cat file2);do if [ $(grep -i $i file1) ];then echo "$i found" >>Matching_lines.txt;else echo "$i missing" >>missing_lines.txt ;fi;done
【讨论】:
见DontReadLinesWithFor。此外,如果您的任何grep
结果扩展到多个单词,或者如果您的任何 file2
条目可以被 shell 视为一个 glob,则此代码的行为将非常糟糕。以上是关于在一个文件中查找不在另一个文件中的行的快速方法?的主要内容,如果未能解决你的问题,请参考以下文章