我有两个大文件(一组文件名)。每个文件大约有3万行。我试图找到一种快速的方法,在file1中查找不存在于file2中的行。

例如,如果这是file1:

line1
line2
line3

这是file2:

line1
line4
line5

那么我的结果/输出应该是:

line2
line3

如此:

Grep -v -f file2 file1

但是在我的大文件上使用时,它非常非常慢。

我怀疑有一个好方法来使用diff(),但输出应该只是行,没有别的,我似乎找不到一个开关。

谁能帮我找到一种快速的方法,使用bash和基本的Linux二进制文件来做到这一点?

编辑:为了跟进我自己的问题,这是我迄今为止发现的使用diff()的最好方法:

 diff file2 file1 | grep '^>' | sed 's/^>\ //'

肯定有更好的办法吧?


排序和差分的速度是多少?

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted

使用fgrep或在grep中添加-F选项可能会有所帮助。但是为了更快的计算,你可以使用Awk。

您可以尝试以下Awk方法之一:

http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219


你可以通过控制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格式说明符。这些选项分别格式化新(添加)、旧(删除)和不变的行。将1设置为空“”可以防止输出这类行。

如果你熟悉统一的差异格式,你可以部分地重新创建它:

diff --old-line-format="-%L" --unchanged-line-format=" %L" \
     --new-line-format="+%L" file1 file2

%L说明符是有问题的行,我们在每一行前面加上“+”“-”或“”,如diff -u (注意,它只输出差异,它缺少每个分组更改顶部的——+++和@@行)。 您还可以使用它来做其他有用的事情,比如用%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操作符确定file1中的行是否存在于file2中。(如果存在重复项,则diff方法的输出将不同。)

如果文件足够大,存储这两个文件会导致内存问题,那么可以用CPU交换内存,方法是只存储file1,并在读取file2时删除匹配项。

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

注意在gawk命令行中-含义的stdin的使用和位置。这是通过从file1中拆分为每次调用20000行的块来提供的。

对于非GNU系统的用户,几乎可以肯定你可以获得一个GNU coreutils包,包括在OSX上作为Apple Xcode工具的一部分,它提供了GNU diff, awk,尽管只是POSIX/BSD的分裂,而不是GNU版本。


$ join -v 1 -t '' file1 file2
line2
line3

t用来比较整行,如果有些行有空格的话。


comm命令(common的缩写)可能很有用,可以逐行比较两个排序好的文件

#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文件实际上是相当可读的。


就像konsolebox建议的,海报grep解决方案

grep -v -f file2 file1

实际上,如果你简单地添加-F选项,就会工作得更好(更快),将模式视为固定的字符串而不是正则表达式。我在一对~1000行文件列表上验证了这一点,我必须进行比较。当将grep输出重定向到wc -l时,使用-F需要0.031秒(实数),而不使用-F需要2.278秒(实数)。

这些测试还包括-x开关,这是解决方案中必要的一部分,以便在file2包含的行与file1中的一行或多行部分匹配(而不是全部)的情况下确保完全准确。

因此,不需要对输入进行排序,快速,灵活(区分大小写等)的解决方案是:

grep -F -x -v -f file2 file1

这并不适用于所有版本的grep,例如,它在macOS中失败,其中文件1中的一行将显示为不存在于文件2中,即使它匹配的另一行是它的子字符串。或者,您可以在macOS上安装GNU grep以使用此解决方案。


你可以使用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())
'

我通常使用——suppress-common-lines标志来做到这一点,但请注意,这只在您以并排格式执行时才有效。

Diff -y——suppress-common-lines file1.txt file2.txt


如果你缺少“花哨的工具”,例如在一些最小的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相比,这也相对较快。


使用moreutils包中的combine,这是一个集实用程序,不支持和、或、异或操作

combine file1 not file2

也就是说,给我在file1中但不在file2中的行

或者给我file1中的行减去file2中的行

注意:在执行任何操作之前,组合排序并在两个文件中找到唯一的行,而diff则不会。你可能会发现diff和combine的输出不同。

所以实际上你是在说

在file1和file2中找到不同的直线,然后用file1中的直线减去file2中的直线

根据我的经验,这比其他选择要快得多


这对我来说似乎很快:

comm -1 -3 <(sort file1.txt) <(sort file2.txt) > output.txt