我有两个大文件(文件名集)。每个文件中大约30.000行。我试图找到一种在file1中查找file2中不存在的行的快速方法。
例如,如果这是文件1:
line1
line2
line3
这是文件2:line1
line4
line5
然后我的结果/输出应该是:line2
line3
这有效:grep -v -f file2 file1
但是在我的大文件上使用时,它非常非常慢。我怀疑有一种使用
diff()
做到这一点的好方法,但是输出应该仅仅是行,没有别的,而且我似乎找不到相应的开关。有人可以使用bash和基本的Linux二进制文件来帮助我找到一种快速的方法吗?
编辑:要跟进我自己的问题,这是到目前为止我使用
diff()
找到的最好方法: diff file2 file1 | grep '^>' | sed 's/^>\ //'
当然,必须有更好的方法吗? 最佳答案
您可以通过控制GNU diff
输出中的旧/新/未更改行的格式来实现此目的:
diff --new-line-format="" --unchanged-line-format="" file1 file2
输入文件应该排序才能正常工作。使用
bash
(和zsh
),您可以使用进程替换<( )
就地排序:diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
在上述情况下,新行和未更改的行被抑制,因此仅输出更改(即您的情况下为已删除的行)。您还可以使用其他解决方案不提供的一些
diff
选项,例如忽略大小写的-i
或用于不太严格匹配的各种空格选项(-E
,-b
,-v
等)。说明
选项
--new-line-format
,--old-line-format
和--unchanged-line-format
使您可以控制diff
格式化差异的方式,类似于printf
格式说明符。这些选项分别格式化新行(添加),旧行(已删除)和未更改的行。将其中一个设置为空“”会阻止此类行的输出。如果您熟悉统一的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
)仅会生成带有排序输入的预期输出,尽管您可以使用<(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
运算符确定file2中是否存在file1中的行。 (如果重复,这将与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扩展名)进行分治,征服file1块重复运行,每次完全读取file2: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版本。