Unix命令在两个文件中查找通用行


178

我敢肯定,我曾经找到一个unix命令,该命令可以从两个或更多文件中打印公共行,有人知道它的名字吗?它比容易得多diff


5
这个问题的答案不一定是每个人都想要的,因为它comm需要排序的输入文件。如果您只想逐行通用,那就太好了。但是,如果您想要我所谓的“ anti-diff”,那就comm做不到。
罗伯特·P·高

@ RobertP.Goldman有一种方法可以在file1包含部分模式(例如pr-123-xy-45file2包含)时在两个文件之间变得通用ec11_orop_pr-123-xy-45.gz。我需要包含的file3ec11_orop_pr-123-xy-45.gz
Chandan Choudhury 2015年

Answers:


216

您要查找的命令是comm。例如:-

comm -12 1.sorted.txt 2.sorted.txt

这里:

-1:取消第1列(1.sorted.txt特有的行)

-2:取消第2列(2.sorted.txt的唯一行)


27
典型用法:通讯-12 1.sorted.txt 2.sorted.txt
Fedir RYKHTIK 2013年

45
当comm需要排序的文件时,您可以使用grep -f file1 file2来获取两个文件的公共行。
Ferdy

2
@ferdy(从您的回答中重复我的评论,因为您的回答本质上是作为评论发布的重复答案)grep会产生一些您可能不会想到的奇怪的事情。具体来说,in中的所有内容都1.txt将解释为正则表达式,而不是纯字符串。另外,中的任何空白行都1.txt将匹配中的所有行2.txt。因此,grep仅在非常特定的情况下有效。您至少要使用fgrep(或grep -f),但空白行可能会在此过程中造成严重破坏。
Christopher Schultz

11
请参见下面的ferdy回答,以及Christopher Schultz回答以及我对此的评论。TL; DR-使用grep -F -x -f file1 file2
Jonathan Leffler

1
@bapors:我提供了一个常见问题解答,内容涉及如何将comm命令的输出分成3个单独的文件? 答案太大了,无法舒适地放在这里。
乔纳森·莱夫勒

61

要轻松地将comm命令应用于未排序的文件,请使用Bash的进程替换

$ bash --version
GNU bash, version 3.2.51(1)-release
Copyright (C) 2007 Free Software Foundation, Inc.
$ cat > abc
123
567
132
$ cat > def
132
777
321

因此,文件abc和def具有共同的一行,即“ 132”。在未排序的文件上使用comm

$ comm abc def
123
    132
567
132
    777
    321
$ comm -12 abc def # No output! The common line is not found
$

最后一行没有输出,没有发现公共行。

现在对已排序的文件使用comm,使用进程替换对文件进行排序:

$ comm <( sort abc ) <( sort def )
123
            132
    321
567
    777
$ comm -12 <( sort abc ) <( sort def )
132

现在我们有了132条线!


2
那么... sort abc > abc.sortedsort dev > def.sorted然后comm -12 abc.sorted def.sorted呢?
Nikana Reklawyks,2017年

1
@NikanaReklawyks然后记住以后要删除临时文件,并在出现错误的情况下进行清理。在许多情况下,过程替换也将更快得多,因为只要结果适合内存,就可以避免磁盘I / O。
三胞胎

29

为了补充Perl单行代码,这里是它的awk等效内容:

awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2

这将从file1阵列中读取所有行arr[],然后检查阵列中file2是否已存在每行(即file1)。找到的行将按照它们在中出现的顺序进行打印file2。请注意,比较in arr使用整行file2作为索引到数组,因此它将仅报告整行的完全匹配。


2
这是正确的答案。其他任何一个都不能使它正常工作(因为我没有尝试过perl)。万分感谢,女士
entonio

1
在显示公共行时保留顺序在某些情况下非常有用,因为这可能会导致排除通讯。
tuxayo

1
如果有人想基于某个列执行相同的操作,但不知道awk,则例如将第5列的两个$ 0替换为$ 5,这样您就可以在第5列的两个文件中共享相同行的行
FatihSarigol,

24

也许你的意思是comm

逐行比较排序后的文件FILE1和FILE2。

如果没有选项,则产生三列输出。第一列包含FILE1独有的行,第二列包含FILE2独有的行,第三列包含两个文件共同的行。

查找这些信息的秘密是信息页面。对于GNU程序,它们比其手册页详细得多。试试看info coreutils,它将列出所有有用的小实用程序。


19

grep -v -f 1.txt 2.txt > 3.txt

为您提供两个文件的区别(2.txt中的内容而不是1.txt中的内容),您可以轻松地执行

grep -f 1.txt 2.txt > 3.txt

收集所有常见的行,这应该为您的问题提供简单的解决方案。如果您对文件进行了排序,则仍应采取措施comm。问候!


2
grep做一些您可能没有想到的奇怪的事情。具体来说,in中的所有内容都1.txt将解释为正则表达式,而不是纯字符串。另外,中的任何空白行都1.txt将匹配中的所有行2.txt。因此,这仅在非常特殊的情况下有效。
Christopher Schultz

13
@ChristopherSchultz:可以使用POSIX grep表示法来升级此答案,使其更好地工作grep,大多数现代Unix变体都支持POSIX 表示法。添加-F(或使用fgrep)抑制正则表达式。添加-x(精确)以仅匹配整行。
乔纳森·勒夫勒

为什么我们要comm处理分类文件?
Ulysse BN

2
@UlysseBN comm可以处理任意大文件,因为它们只需要排序即可,因为它只需要在内存中保留三行(我猜GNU comm甚至知道如果行真的很长也只能保留一个前缀)。该grep解决方案需要让所有的搜索表达式在内存中。
Tripleee'December

8
perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

这比comm命令更好,因为它搜索file1in的每一行,file2其中comm仅当line nin file1等于line nin 时才进行比较file2
teriiehina 2014年

1
@teriiehina:不;comm不能简单地将file1中的N行与file2中的N行进行比较。它可以很好地管理在两个文件中插入的一系列行(当然,这等效于从另一个文件中删除一系列的行)。它仅要求输入按排序顺序。
Jonathan Leffler

comm如果要保留订单,总比回答好。awk如果不想重复,总比回答好。
tuxayo


8

如果这两个文件尚未排序,则可以使用:

comm -12 <(sort a.txt) <(sort b.txt)

它将起作用,避免comm: file 2 is not in sorted order 这样做时出现错误消息comm -12 a.txt b.txt


您是对的,但这实际上是在重复另一个答案,实际上并没有任何好处。如果您决定回答一个已经建立并正确答案的较早的问题,那么在当天晚些时候添加一个新答案可能不会给您任何功劳。如果您有一些与众不同的新信息,或者您确信其他答案都错了,则一定要添加一个新答案,但是“又一个答案”在问了很长时间后给出相同的基本信息通常是不会的。不能赚很多钱。
乔纳森·莱夫勒

我什至没有看到这个答案@JonathanLeffler,因为这部分是答案的最后部分,与之前答案的其他元素混合在一起。尽管另一个答案更为精确,但我认为我的好处是,对于那些想要快速解决方案的人,只需阅读两行即可。有时我们正在寻找详细的答案,有时我们很着急,可以快速阅读的随时粘贴的答案就可以了。
巴吉(Basj)

我也不在乎信用/代表,我没有为此目的发布。
巴吉(Basj)

1
还要注意<(command),尽管进程替换语法可以在Bash和其他一些应用程序中使用,但它不能移植到POSIX Shell中。
三胞胎


3

在受限版本的Linux上(例如我正在开发的QNAP(nas)):

  • 通讯不存在
  • grep -f file1 file2可能会导致某些问题,如@ChristopherSchultz所说,使用grep -F -f file1 file2速度真的很慢(超过5分钟-未完成-超过2-3秒使用下面的方法处理超过20MB的文件)

所以这就是我所做的:

sort file1 > file1.sorted
sort file2 > file2.sorted

diff file1.sorted file2.sorted | grep "<" | sed 's/^< *//' > files.diff
diff file1.sorted files.diff | grep "<" | sed 's/^< *//' > files.same.sorted

如果files.same.sorted应该以与原始顺序相同的顺序,则以与file1相同的顺序添加此行:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same

或者,与file2相同的顺序:

awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same

2

仅供参考,如果有人仍在寻找如何对多个文件执行此操作,请参阅在多个文件中查找匹配行的链接答案


结合这两个答案(ans1ans2),我认为无需排序文件就可以得到所需的结果:

#!/bin/bash
ans="matching_lines"

for file1 in *
do 
    for file2 in *
        do 
            if  [ "$file1" != "$ans" ] && [ "$file2" != "$ans" ] && [ "$file1" != "$file2" ] ; then
                echo "Comparing: $file1 $file2 ..." >> $ans
                perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' $file1 $file2 >> $ans
            fi
         done 
done

只需保存它,赋予它执行权限(chmod +x compareFiles.sh)并运行它。它将获取当前工作目录中存在的所有文件,并进行全对所有比较,结果保留在“ matching_lines”文件中。

有待改进的地方:

  • 跳过目录
  • 避免两次比较所有文件(file1 vs file2和file2 vs file1)。
  • 也许在匹配字符串旁边添加行号

-2
rm file3.txt

cat file1.out | while read line1
do
        cat file2.out | while read line2
        do
                if [[ $line1 == $line2 ]]; then
                        echo $line1 >>file3.out
                fi
        done
done

这应该做。


1
rm -f file3.txt如果您要删除文件,则应该使用;如果文件不存在,则不会报告任何错误。OTOH,如果您的脚本只是简单地回显到标准输出,则无需用户让脚本用户选择输出应到达的位置。最终,您可能希望使用$1$2(命令行参数)而不是固定文件名(file1.outfile2.out)。剩下的就是算法:它将变得很慢。它将file2.out为中的每一行读取一次file1.out。如果文件很大(例如多个千字节),它将很慢。
乔纳森·莱夫勒

虽然如果您输入的内容中不包含任何外壳程序元字符,这在名义上是可行的(提示:请参阅从shellcheck.net得到的警告),但是这种幼稚的方法效率极低。像grep -F这样的工具可以将一个文件读入内存,然后在另一个文件上进行一次传递,从而避免在两个输入文件上重复循环。
三胞胎
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.