322

我有两个大文件(文件名集)。每个文件大约有 30.000 行。我正在尝试找到一种快速方法来查找 file1 中不存在于 file2 中的行。

例如,如果这是file1:

line1
line2
line3

这是文件2:

line1
line4
line5

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

line2
line3

这有效:

grep -v -f file2 file1

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

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

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

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

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

当然,一定有更好的方法吗?

4

12 回答 12

333

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文件实际上是非常可读的。

于 2014-10-28T21:46:19.093 回答
285

diff您可以通过控制 GNU输出中旧/新/未更改行的格式来实现此目的:

diff --new-line-format="" --unchanged-line-format=""  file1 file2

应该对输入文件进行排序以使其正常工作。使用bash(and zsh) 您可以使用流程替换进行就地排序<( )

diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)

在上面的行和未更改行被抑制,因此仅输出更改(即在您的情况下删除的行)。您还可以使用diff其他解决方案不提供的一些选项,例如-i忽略大小写或各种空格选项(-E,-b-v)以实现不太严格的匹配。


解释

options --new-line-format--old-line-format--unchanged-line-format让您控制diff格式差异的方式,类似于 printf格式说明符。这些选项分别格式化(添加)、(删除)和未更改的行。将一个设置为空 "" 可以防止输出那种行。

如果您熟悉统一差异格式,则可以使用以下方法部分重新创建它:

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

说明%L符是有问题的行,我们在每个前面加上“+”“-”或“”,就像diff -u (注意它只输出差异,它缺少每个分组更改顶部的--- +++和行)。@@您还可以使用它来做其他有用的事情,例如用.为每行编号%dn


diff方法(连同其他建议comm和)仅产生带有排序join输入的预期输出,但您可以使用它来进行就地排序。这是一个简单的(nawk)脚本(受 Konsolebox 答案中链接到的脚本的启发),它接受任意排序的输入文件,按照它们在 file1 中出现的顺序输出缺失的行。<(sort ...)awk

# 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 的全部内容存储在两个数组中,一个由 line numberll1[]索引,一个由 line content 索引ss1[]ll1[]然后在读取 file2 时,从和中删除每个匹配的行ss1[]。最后,输出 file1 中的剩余行,保留原始顺序。

在这种情况下,对于上述问题,您还可以使用 GNU分而治split之(过滤是 GNU 扩展),使用 file1 的块重复运行并每次完全读取 file2:

split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1

注意命令行中-含义stdin的使用和位置。gawk这是由splitfile1 以每次调用 20000 行的块提供的。

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

于 2013-08-13T09:24:51.190 回答
40

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

grep -v -f file2 file1

-F如果您简单地添加选项,将模式视为固定字符串而不是正则表达式,实际上效果很好(更快) 。我在一对我必须比较的 ~1000 行文件列表上验证了这一点。在-F将 grep 输出重定向到wc -l.

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

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

grep -F -x -v -f file2 file1

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

于 2016-07-05T16:35:22.660 回答
22

如果您缺少“花哨的工具”,例如在一些最小的 Linux 发行版中,有一个解决方案,只有cat,sortuniq

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较快。

于 2018-11-22T08:08:48.340 回答
12

使用combinefrom package,一个支持, , ,操作moreutils的集合实用程序notandorxor

combine file1 not file2

即给我在file1中但不在file2中的行

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

注意: combine在执行任何操作之前对两个文件中的唯一行进行排序和查找,但diff不会。diff因此,您可能会发现和的输出之间存在差异combine

所以实际上你是在说

在 file1 和 file2 中找到不同的行,然后给我 file1 中的行减去 file2 中的行

根据我的经验,它比其他选项快得多

于 2019-10-03T02:32:07.357 回答
11

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

sort file1 -u > file1.sorted
sort file2 -u > file2.sorted
diff file1.sorted file2.sorted
于 2013-08-13T09:12:44.327 回答
6
$ join -v 1 -t '' file1 file2
line2
line3

如果-t您在某些行中有空格,请确保它比较整行。

于 2014-08-31T08:49:14.983 回答
4

您可以使用 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())
'
于 2017-08-10T07:24:44.003 回答
4

这对我来说似乎很快:

comm -1 -3 <(sort file1.txt) <(sort file2.txt) > output.txt
于 2020-10-25T07:32:32.227 回答
2

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

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

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

于 2013-08-13T09:17:44.040 回答
1

我通常这样做的方式是使用--suppress-common-lines标志,但请注意,这仅在您以并排格式执行时才有效。

diff -y --suppress-common-lines file1.txt file2.txt

于 2018-03-13T16:22:19.667 回答
-2

我发现对我来说,使用普通的 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
于 2018-05-01T16:26:28.930 回答