如何显示共同行(反向差异)?

221

我有一系列的文本文件,想知道它们之间相同的行,而不是不同的行。命令行Unix或Windows都可以。

文件foo

linux-vdso.so.1 =>  (0x00007fffccffe000)
libvlc.so.2 => /usr/lib/libvlc.so.2 (0x00007f0dc4b0b000)
libvlccore.so.0 => /usr/lib/libvlccore.so.0 (0x00007f0dc483f000)
libc.so.6 => /lib/libc.so.6 (0x00007f0dc44cd000)

文件 bar:

libkdeui.so.5 => /usr/lib/libkdeui.so.5 (0x00007f716ae22000)
libkio.so.5 => /usr/lib/libkio.so.5 (0x00007f716a96d000)
linux-vdso.so.1 =>  (0x00007fffccffe000)

因此,鉴于上述这两个文件,所需实用程序的输出类似于file1:line_number, file2:line_number == matching text(仅是建议;我真的不在乎语法):

foo:1, bar:3 == linux-vdso.so.1 =>  (0x00007fffccffe000)

@ChristopherSchultz 对不起,我的错误。第一个示例中的第一行应该与第二个示例中的最后一行匹配。感谢您发现了这个错误,我会进行更改。 - matt wilkie
2
另一个类似的问题,有很好的回答:http://unix.stackexchange.com/questions/1079/output-the-common-lines-similarities-of-two-text-files-the-opposite-of-diff - MortezaE
更一般的解决方案:我们应该提交一个补丁到GNU diffutils,以添加一个选项,因为这实际上只是在相等测试中的一个微不足道的否定。 - anon
如果有人有兴趣编写这样的补丁:我刚刚仔细查看了diff的源代码,它并不是一件简单的事情,因为它非常庞大而且混乱。也没有错误跟踪器,只有一个邮件列表。所以我能做的最好建议是通过邮件请求。 (尽管我建议从头开始进行干净的重写。我的眼睛仍然很疼。;)) - anon
8个回答

256

在*nix系统中,你可以使用comm命令。该问题的答案是:

comm -1 -2 file1.sorted file2.sorted 
# where file1 and file2 are sorted and piped into *.sorted

下面是comm的完整用法:

comm [-1] [-2] [-3 ] file1 file2
-1 Suppress the output column of lines unique to file1.
-2 Suppress the output column of lines unique to file2.
-3 Suppress the output column of lines duplicated in file1 and file2. 

另外请注意,在使用 comm 命令之前,按照手册中提到的方式对文件进行排序非常重要。


3
comm [-1] [-2] [-3] file1 file2-1 隐藏只存在于file1中的独特行的输出列。 -2 隐藏只存在于file2中的独特行的输出列。 -3 隐藏在file1和file2中重复的行的输出列。 - ojblass
10
在使用comm之前,我发现将文件排序很重要。也许可以将此添加到答案中。 - matt wilkie
12
回答这个问题的简短方式是:在终端中运行“comm -1 -2 file1 file2”命令。该命令将显示两个文件之间不同行的交集。 - greggles
10
如果您的文件未排序,可以使用以下命令:comm -1 -2 <(sort filename1) <(sort filename2)。 - Kevin Wheeler
1
而且,sort -u file1 > file1.sorted(--unique)的输出将不会有任何重复的行。 - Max Power
显示剩余3条评论

82

我在一个被列为重复问题的问题中找到了这个答案。我发现与comm相比,grep更适合管理员使用。因此,如果你只需要匹配行的集合(例如用于比较CSV文件),只需使用以下命令即可:

grep -F -x -f file1 file2

或者使用简化的fgrep版本:

fgrep -xf file1 file2

此外,您可以使用 file2 * 通配符并查找多个文件中共有的行,而不仅仅是两个文件。
一些其他方便的变体包括:
  • -n标记显示每个匹配行的行号
  • -c仅计算匹配行数
  • -v仅显示与 file2 不同的行(或使用 diff )。
使用 comm 更快,但这种速度是以需要先对文件进行排序为代价的。作为“反向diff”并不是非常有用。

2
谢谢Ryder,这对许多人来说可能比comm更有用。您应该链接到源答案(在右侧导航中有超过半打链接;找起来有点麻烦)。了解grep在未排序或不同排序的输入中的表现如何,并且可以打印匹配行号也是很好的。 - matt wilkie
3
@mattwilkie,我觉得有必要回来澄清一下使用“-v”标志的用法,因为我自己在使用时出现了失误。假设你有两个CSV文件file1和file2,它们既有重叠行也有非重叠行。如果你想要所有且仅有非重叠行,使用“fgrep -v file1 file2”将只返回file2中的非重叠行,而不包括file1中额外的非重叠行。这对一些人来说可能是显而易见的,但明确表述比冒险产生误解更好。在这种特殊情况下,排序文件并使用“comm”仍然是更好的选择。 - Ryder
2
感谢您回来并澄清Ryder。我们注意到并感激您的额外关注(很容易让旧事物消失!)。我已经更改了接受的答案,因为comm显然是社区的选择,即使在不需要排序时个人仍然使用它以避免额外开销。 - matt wilkie
2
使用 grep 时的另一个复杂情况是:第一个文件中的任何空行都将与第二个文件中的每一行匹配。确保 file1 中没有空行,否则它看起来就像两个文件是相同的。 - Christopher Schultz
我认为这比comm更好,因为它能够捕捉到两个不同源代码之间更多的相似行。我的想法是,我想确定两个源文件在它们的过去版本中是否有关联。 - daparic
显示剩余3条评论

36

1
谢谢。我本来想接受两个答案,因为 Perl 一行代码可以跨平台。但是 Comm 更简单,所以我选择了它。 - matt wilkie
1
完美。在Windows上使用Cygwin终端,comm命令不容易获得。这是一个完美的替代方案。 - Qix - MONICA WAS MISTREATED
3
不考虑行的顺序,这比 comm 命令更准确。 - enl8enmentnow
1
请看这里的解释:https://dev59.com/U2Mm5IYBdhLWcg3whPTq - Chris Koknat

24

我刚从答案中学到了 comm 命令,但我想补充一些内容:如果文件没有排序,并且您不想触碰原始文件,您可以将输出传送到 sort 命令。这样可以保留原文件的完整性。在Bash中可以使用,但其他shell则不确定。

comm -1 -2 <(sort file1) <(sort file2)

这也可以扩展到比较命令输出,而不是文件:

comm -1 -2 <(ls /dir1 | sort) <(ls /dir2 | sort)

问题在于,您可能不希望结果被排序,比如程序代码文件。实际上,“diff”应该有一个选项来解决这个问题,就像“patch”有“-r”选项来反转事物一样。 - anon

13

最简单的方式是:

awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2

文件不需要被排序。


3
与这里的大多数答案不同,它允许您重构源模板。我有两个文件,它们都使用同一个包装器构建,但在几个地方插入了不同的文本。这个答案使我能够恢复包装器。 - Lucas Gonze
1
可以在这个问题 https://dev59.com/xlwY5IYBdhLWcg3wkIYi 或者在其中一个评论中引用的惯用 AWK 博客中找到解释。 - Tomáš Záluský

7

我认为diff工具本身可以利用其统一(-U)选项来实现此效果。因为diff的输出的第一列标记了该行是添加还是删除,我们可以寻找那些未更改的行。

diff -U1000 file_1 file_2 | grep '^ '

数字1000被任意选择,足够大,比任何单个的diff输出都要大。

以下是完整、可靠的一组命令:

f1="file_1"
f2="file_2"

lc1=$(wc -l "$f1" | cut -f1 -d' ')
lc2=$(wc -l "$f2" | cut -f1 -d' ')
lcmax=$(( lc1 > lc2 ? lc1 : lc2 ))

diff -U$lcmax "$f1" "$f2" | grep '^ ' | less

# Alternatively, use this grep to ignore the lines starting
# with +, -, and @ signs.
#   grep -vE '^[+@-]'

如果您想要包含那些只是移动了位置的行,您可以在执行diff操作之前对输入进行排序,如下所示:
f1="file_1"
f2="file_2"

lc1=$(wc -l "$f1" | cut -f1 -d' ')
lc2=$(wc -l "$f2" | cut -f1 -d' ')
lcmax=$(( lc1 > lc2 ? lc1 : lc2 ))

diff -U$lcmax <(sort "$f1") <(sort "$f2") | grep '^ ' | less

1

仅供参考,我制作了一个小工具,可以在Windows上执行与“grep -F -x -f file1 file2”相同的操作(因为我没有找到任何相当于此命令的东西)

这是它的链接: http://www.nerdzcore.com/?page=commonlines

使用方法是“CommonLines inputFile1 inputFile2 outputFile”

源代码也可用(GPL许可证)。


1
在Windows中,您可以使用带有CompareObject的PowerShell脚本:
compare-object -IncludeEqual -ExcludeDifferent -PassThru (get-content A.txt) (get-content B.txt)> MATCHING.txt | Out-Null #Find Matching Lines

比较对象:
  • 包括相同项而无排除不同项:全部
  • 排除不同项而无包括相同项:无

“Out-Null”的目的是什么? - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接