在一个文件中查找不在另一个文件中的行的快速方法?


241

我有两个大文件(文件名集)。每个文件中大约30.000行。我试图找到一种快速的方法来查找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/^>\ //'

当然,必须有更好的方法吗?


1
如果速度更快,您可以尝试以下方法:awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
肯特


4
感谢您介绍grep -v -f file2 file1
Rahul Prasad


减少工具集的简单方法:cat file1 file2 file2 | sort | uniq --unique,请参阅下面的答案。
OndraŽižka'18

Answers:


233

您可以通过控制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 (注意,只是输出不同,它缺乏--- +++@@线在每个分组改变的顶部)。您也可以使用此做其他有用的东西像数每行%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[]由行content索引ss1[]。然后在读取file2时,从ll1[]和中删除每个匹配的行ss1[]。最后,输出文件1的其余行,并保留原始顺序。

在这种情况下,与如所陈述的问题,也可以分而治之使用GNU split(过滤是GNU扩展)中,用file1和读取文件2完全每次的块重复运行:

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

需要注意的使用和放置-意义stdin上的gawk命令行。这是由splitfile1以每次调用20000行的数据块提供的。

对于非GNU系统的用户,几乎肯定是GNU coreutils软件包可以获取,包括OSX的部分苹果的Xcode工具,它提供了GNU diffawk虽然只有一个POSIX / BSD split而不是一个GNU版本。


1
这完全满足了我的需求,而这只花费了巨大grep所花费时间的一小部分。谢谢!
Niels2000

1
找到了这个gnu手册页
Juto

我们中有些人不在gnu上[此处是OS X bsd ...] :)
rogerdpack 2015年

1
我假设您的意思是diff:通常,输入文件将有所不同,diff在这种情况下将返回1 。认为这是一种奖励;-)如果在shell脚本中进行测试,则预期0和1是预期的退出代码,则2表示有问题。
spuratic先生

1
@ mr.spuratic啊,是的,现在我在中找到了man diff。谢谢!
Archeosudoerus

245

通讯命令(简称“常用”)可能是有用的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文件实际上对此非常可读。


6
在OSX上完美运行。
pisaruk

41
也许应该强调对分类输入的要求。
Tripleee '17

21
comm还具有一个选项来验证输入是否已排序--check-order(无论如何似乎都可以,但是此选项将导致输入错误而不是继续)。但是,要对文件进行排序,只需执行以下操作:com -23 <(sort file1) <(sort file2)等等
迈克尔(Michael)

我正在将Windows生成的文件与Linux生成的文件进行比较,似乎comm根本无法正常工作。我花了一段时间才弄清楚这与行尾有关:即使看起来相同的行,即使它们具有不同的行尾也被认为是不同的。该命令dos2unix可用于仅将CRLF线路末端转换为LF。
ZeroOne

23

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

grep -v -f file2 file1

如果仅添加-F选项,将模式视为固定字符串而不是正则表达式,则实际上效果很好(快速)。我在必须比较的一对〜1000行文件列表中对此进行了验证。-F将grep输出重定向到时,使用了0.031秒(真实),而没有使用了2.278秒(真实)wc -l

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

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

grep -F -x -v -f file2 file1

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


是的,它可以工作,但是即使-F这样也不能很好地扩展。
Molomby

这不是那么快,在放弃之前,我等了5分钟等待2个文件(约50万行)
cahen

实际上,这种方式仍然比comm方式慢,因为这种方式可以处理未排序的文件,因此由于未排序而被拖下,comm利用了排序的优势
workplaylifecycle

@workplaylifecycle您需要添加排序时间,这可能是极大的瓶颈file2
rwst

但是,带有该-x选项的grep 显然会占用更多内存。随着file26-10含180M字节的话我的过程有Killed一个32GB的RAM机器上...
RWST

11

排序和比较的速度如何?

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

1
感谢您提醒我在进行差异处理之前需要对文件进行排序。sort + diff快得多。
Niels2000

4
一个衬里;-) diff <(排序文件1 -u)<(排序文件2 -u)
steveinatorx

11

如果你是短期的一些最低限度的Linux发行版“神奇工具”,例如,有只有一个解决方案catsort以及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


1
注意-一些实现将无法识别该--unique选项。您应该能够为此使用标准化的POSIX选项| uniq -u
AndrewF

1
在示例中,“ 2”来自哪里?
Niels2000

1
@ Niels2000,seq 1 1 7从1开始创建数字,以1为增量,直到7,即1 2 3 4 5 67。然后右边是您的2!
艾里克·莱格里

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

-t确保它的整体线条比较,如果你有一些行的空间。


像一样commjoin要求将两个输入行都在要执行联接操作的字段上进行排序。
Tripleee '17

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())
'

4

使用combine来自moreutils包,集实用,支持notandorxor操作

combine file1 not file2

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

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

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

所以实际上你是在说

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

以我的经验,它比其他选择要快得多



1

我通常这样做的方法是使用--suppress-common-lines标志,但是请注意,只有当您以并排格式进行操作时,此标志才有效。

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


0

我发现对我来说,使用常规的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

2
参见DontReadLinesWithFor。另外,如果您的任何grep结果扩展为多个单词,或者如果您的任何file2条目都可以被shell视为glob ,则此代码的行为将非常糟糕。
查尔斯·达菲
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.