我有两个大文件(文件名集)。每个文件中大约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方法(以及其他建议commjoin)仅会生成带有排序输入的预期输出,尽管您可以使用<(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

注意-的用法和位置,即stdingawk命令行上。这是由来自file1的split以每次调用20000行的数据块提供的。

对于非GNU系统上的用户,几乎可以肯定会获得一个GNU coreutils软件包,包括在OSX上作为Apple Xcode工具的一部分,该工具提供GNU diffawk,但仅提供POSIX/BSD split而不是GNU版本。

10-07 13:13