Unix command to find lines common in two files
我确定我曾经发现一个unix命令可以打印两个或多个文件中的公共行,有人知道它的名字吗? 它比diff简单得多。
-
这个问题的答案不一定是每个人都想要的,因为comm需要排序的输入文件。 如果您只想逐行排列,那就太棒了。 但如果你想要我称之为"反差异"的东西,那么comm就不能胜任。
-
当Robert1包含像pr-123-xy-45这样的部分模式而file2包含ec11_orop_pr-123-xy-45.gz时,@ RobertP.Goldman有一种在两个文件之间通用的方法。 我需要包含ec11_orop_pr-123-xy-45.gz的file3
-
请参阅此选项以逐行排序文本文件
您正在寻找的命令是comm。例如:-
1
| comm -12 1.sorted.txt 2.sorted.txt |
这里:
-1:禁止第1列(1.sorted.txt唯一的行)
-2:抑制第2列(2.sorted.txt唯一的行)
-
典型用法:comm -12 1.sorted.txt 2.sorted.txt
-
虽然comm需要排序文件,但您可以使用grep -f file1 file2来获取两个文件的公共行。
-
@ferdy(从你的回答中重复我的评论,因为你的评论基本上是作为评论发布的重复答案)grep做了一些你可能没想到的奇怪的事情。具体来说,1.txt中的所有内容都将被解释为正则表达式而不是纯字符串。此外,1.txt中的任何空行都将匹配2.txt中的所有行。所以grep只能在非常特殊的情况下使用。你至少想要使用fgrep(或grep -f),但空白行可能会对这个过程造成严重破坏。
-
请参阅下面的ferdy的答案,以及Christopher Schultz和我对它的评论。 TL; DR - 使用grep -F -x -f file1 file2。
-
@JonathanLeffler如何在不同的文件中输出?
-
@bapors:我不确定你在问什么。如果你只想在一个文件中的File1中的行,那些只在另一个文件中的文件中的行,以及那些在三分之一中的行,那么(假设文件中的所有行都没有以制表符开头)你可以使用sed来将输出拆分为三个文件。但这就是你问的问题吗?
-
@JonathanLeffler是的,这正是我的要求。我对sed不是很有信心,如果可以,你会展示一个例子吗?
-
@bapors:我提供了一个自我回答的问答作为如何将comm命令的输出转换为3个单独的文件?答案太大了,不适合在这里舒适。
-
@JonathanLeffler非常感谢你!我很感激!
-
它是否要求文件具有相同数量的行?
-
@ Hi-Angel - 不,文件可以是不同的大小。
要轻松将comm命令应用于未排序的文件,请使用Bash的进程替换:
1 2 3 4 5 6 7 8 9 10 11
| $ 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:
1 2 3 4 5 6 7 8 9
| $ comm abc def
123
132
567
132
777
321
$ comm -12 abc def # No output! The common line is not found
$ |
最后一行没有产生输出,没有发现公共线。
现在使用comm对已排序的文件,使用进程替换对文件进行排序:
1 2 3 4 5 6 7 8
| $ comm <( sort abc ) <( sort def )
123
132
321
567
777
$ comm -12 <( sort abc ) <( sort def )
132 |
现在我们得到了132线!
-
所以... sort abc > abc.sorted,sort dev > def.sorted然后comm -12 abc.sorted def.sorted?
-
@NikanaReklawyks然后记得在之后删除临时文件,并在出现错误时应对清理。在许多情况下,进程替换也会快得多,因为只要结果适合内存,就可以避免磁盘I / O.
为了补充Perl单线程,这里是awk等价物:
1
| awk 'NR==FNR{arr[$0];next} $0 in arr' file1 file2 |
这将读取从file1到数组arr[]的所有行,然后检查file2中的每一行是否已存在于数组中(即file1)。找到的行将按照它们在file2中出现的顺序打印。
请注意,比较in arr使用file2中的整行作为数组的索引,因此它仅报告整行的完全匹配。
-
这(!)是正确的答案。没有其他人可以普遍工作(我没有尝试perl,因为)。万分感谢女士
-
在显示公共行时保留顺序在某些情况下非常有用,因为这会排除comm。
-
如果有人想根据某个列做同样的事情但不知道awk,只需将$ 0替换为$ 5,例如第5列,这样你就可以在第5列中的2个文件中共享相同的行
也许你的意思是comm?
Compare sorted files FILE1 and FILE2 line by line.
With no options, produce three-column output. Column one
contains lines unique to FILE1, column
two contains lines unique to
FILE2, and column three contains lines common to both files.
查找这些信息的秘诀是信息页面。对于GNU程序,它们比人工页面更详细。尝试info coreutils,它会列出所有小的有用工具。
而
1
| grep -v -f 1.txt 2.txt > 3.txt |
给你两个文件的差异(2.txt中的内容,而不是1.txt中的内容),你可以很容易地做到
1
| grep -f 1.txt 2.txt > 3.txt |
收集所有常见的行,这应该为您的问题提供简单的解决方案。如果您已经排序了文件,那么您应该使用comm。问候!
-
grep会做一些你可能没想到的奇怪的事情。具体来说,1.txt中的所有内容都将被解释为正则表达式而不是纯字符串。此外,1.txt中的任何空行都将匹配2.txt中的所有行。所以这只适用于非常具体的情况。
-
@ChristopherSchultz:有可能使用POSIX grep表示法升级这个答案,以便在大多数现代Unix变种中找到grep。添加-F(或使用fgrep)来抑制正则表达式。添加-x(精确)以仅匹配整行。
-
我们为什么要将comm用于排序文件?
-
@UlysseBN comm可以处理任意大的文件,只要它们被排序,因为它只需要在内存中保存三行(我猜GNU comm甚至知道只保留一个前缀,如果这些行是真的长)。 grep解决方案需要将所有搜索表达式保留在内存中。
如果这两个文件尚未排序,您可以使用:
1
| comm -12 <(sort a.txt) <(sort b.txt) |
并且它将工作,在执行comm -12 a.txt b.txt时避免错误消息comm: file 2 is not in sorted order
。
-
你是对的,但这基本上是在重复另一个答案,这实际上并没有带来任何好处。如果您决定回答一个陈旧且正确答案的旧问题,那么在当天晚些时候添加新答案可能不会给您任何信任。如果您有一些独特的新信息,或者您确信其他答案都是错误的,那么无论如何都要添加一个新的答案,但是"又一个答案"会在询问问题后很长一段时间内提供相同的基本信息为你赢得很多荣誉。
-
我甚至没有看到这个答案@JonathanLeffler,因为这部分是在答案的最后,与之前的其他答案元素混合在一起。虽然另一个答案更准确,但我认为我的好处是,对于想要快速解决方案的人来说,只有2行可供阅读。有时我们正在寻找详细的答案,有时我们很匆忙,快速阅读准备好的答案很好。
-
我也不关心信用/代表,我没有为此目的发帖。
-
还要注意,进程替换语法<(command)不能移植到POSIX shell,尽管它可以在Bash和其他一些工作。
1
| perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' file1 file2 |
-
这比comm命令工作得更好,因为它在file2中搜索file1的每一行,其中comm只会比较file1中的行n是否等于file2中的行n 。
-
@teriiehina:不; comm不是简单地将file1中的行N与file2中的行N进行比较。它可以很好地管理插入任一文件中的一系列行(当然,这相当于从另一个文件中删除一系列行)。它只需要输入按排序顺序。
-
如果想要保留订单,优于comm的答案。如果不想要重复,则优于awk。
-
解释如下:stackoverflow.com/questions/17552789/
1
| awk 'NR==FNR{a[$1]++;next} a[$1] ' file1 file2 |
在Linux的有限版本上(就像我正在研究的QNAP(nas)):
-
comm不存在
-
grep -f file1 file2可能会导致一些问题,如@ChristopherSchultz所说,使用grep -F -f file1 file2非常慢(超过5分钟 - 没有完成 - 超过2-3秒,使用下面的方法,文件超过20MB)
所以这就是我所做的:
1 2 3 4 5
| 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相同的顺序:
1
| awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file1 > files.same |
或者,与file2相同的顺序:
1
| awk 'FNR==NR {a[$0]=$0; next}; $0 in a {print a[$0]}' files.same.sorted file2 > files.same |
仅供参考,如果有人仍在查看如何为多个文件执行此操作,请参阅查找多个文件中的匹配行的链接答案。
结合这两个答案(ans1和ans2),我认为你可以得到你需要的结果,而无需对文件进行排序:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #!/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)。
-
也许在匹配字符串旁边添加行号
1 2 3 4 5 6 7 8 9 10 11
| 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 |
这应该做到这一点。
-
试图格式化你的代码 - 请检查,编辑和改进:-)
-
谢谢你格式化它。
-
如果你要删除文件,你应该使用rm -f file3.txt;如果文件不存在,则不会报告任何错误。 OTOH,如果您的脚本只是回显标准输出,让脚本用户选择输出应该去的地方就没有必要了。最终,您可能希望使用$1和$2(命令行参数)而不是固定文件名(file1.out和file2.out)。这离开了算法:它会变慢。对于file1.out中的每一行,它将读取file2.out一次。如果文件很大(比如几千字节),它会很慢。
-
虽然如果你有不包含任何shell元字符的输入,这名义上可以工作(提示:看看你从shellcheck.net得到什么警告),这种天真的方法是非常低效的。像grep -F这样的工具,它将一个文件读入内存,然后对另一个文件进行单次传递,避免在两个输入文件上重复循环。