Unix命令查找两个文件中常见的行的问题有一个答案,build议使用comm
命令来执行任务:
comm -12 1.sorted.txt 2.sorted.txt
这显示了两个文件共同的行( -1
压制仅在第一个文件中的行, -2
压缩仅在第二个文件中的行,只留下两个文件共同的行作为输出)。 如文件名称所示,input文件必须按sorting顺序。
在对这个问题的评论中, 水煤浆问道:
如何将输出在不同的文件?
为了澄清,我问:
如果你只想在一个文件中只有File1的行,那么只有在File2中的行在另一个文件中,而那些在第三个文件中的行,那么(如果文件中没有行以tab开始),你可以使用
sed
来分割输出到三个文件。
用户资料证实:
这正是我所问的。 你会举一个例子吗?
答案是相对冗长的,会破坏其他问题答案的简单性(淹没了大量的信息),所以我在这里单独提出了这个问题,并提供了一个答案。
使用sed
的基本解决方案依赖于comm
输出行只在第一个没有前缀的文件中找到; 它只输出第二个文件中只有一个标签的行; 并使用两个选项卡输出在两个文件中找到的行。
它也依靠sed
的w
命令写入文件。
给定文件1.sorted.txt
包含:
1.line-1 1.line-2 1.line-4 1.line-6 2.line-2 3.line-5
和文件2.sorted.txt
包含:
1.line-3 2.line-1 2.line-2 2.line-4 2.line-6 3.line-5
comm 1.sorted.txt 2.sorted.txt
的基本输出是:
1.line-1 1.line-2 1.line-3 1.line-4 1.line-6 2.line-1 2.line-2 2.line-4 2.line-6 3.line-5
给定一个文件script.sed
包含:
/^\t\t/ { s/// w file.3 d } /^\t/ { s/// w file.2 d } /^[^\t]/ { w file.1 d }
你可以运行如下所示的命令并获得所需的输出:
$ comm 1.sorted.txt 2.sorted.txt | sed -f script.sed $ cat file.1 1.line-1 1.line-2 1.line-4 1.line-6 $ cat file.2 1.line-3 2.line-1 2.line-4 2.line-6 $ cat file.3 2.line-2 3.line-5 $
该脚本的工作原理是:
file.3
和删除行(因此忽略脚本的其余部分), file.2
,并删除行(因此脚本的其余部分将被忽略), file.1
,并删除行。 步骤3中的匹配和删除操作比其他任何操作都更为对称。 他们可以省略(只留下w file.1
),这个脚本的工作原理是一样的。 不过,请参阅下面的script3.sed
进一步的理由来保持对称性。
正如所写,这需要GNU sed
; BSD sed
不能识别\t
转义。 很显然,这个文件可以用实际的标签来代替\t
表示,然后BSD sed
就可以正常运行了。
有可能使它在命令行上全部工作,但它很烦琐(而且这是有礼貌的)。 使用Bash的ANSI C引用 ,你可以写:
$ comm 1.sorted.txt 2.sorted.txt | > sed -e $'/^\t\t/ { s///\nw file.3\nd\n }' \ > -e $'/^\t/ { s///\nw file.2\nd\n }' \ > -e $'/^[^\t]/ { w file.1\nd\n }' $
它将script.sed
的三个“段落”中的每一个写入单独的-e
选项中。 w
命令很挑剔; 它需要在脚本的同一行之后的文件名和文件名,因此在脚本中的文件名之后使用\n
。 有很多可以消除的空间,但是显示的布局对称更清晰。 而使用-f script.sed
文件可能更简单一些 – 这当然是值得了解的技术,因为它可以避免sed
脚本必须在单引号,双引号和后引号上操作的问题,这使得难以在脚本上编写脚本Bash命令行。
最后,如果这两个文件可以包含以制表符开头的行,这种技术需要更强大的力量才能使其工作。 一个变体解决方案利用Bash的进程替换在文件中的行之前添加前缀,然后在写入输出文件之前,后处理sed
脚本删除前缀。
script3.sed
(用tab取代最多8个空格) – 注意,这次在第三段中有一个替代s///
( d
仍然是可选的,但也可以包含):
/^ X/ { s/// w file.3 d } /^ X/ { s/// w file.2 d } /^X/ { s/// w file.1 d }
和命令行:
$ comm <(sed 's/^/X/' 1.sorted.txt) <(sed 's/^/X/' 2.sorted.txt) | > sed -f script3.sed $
对于相同的输入文件,这会产生相同的输出,但是通过在每行的开始处添加并移除X
,代码不会更改数据的排序顺序,并且会处理前导制表符(如果存在)。
您也可以轻松地编写使用Perl或Awk的解决方案,甚至不需要使用comm
(并且可以使用未分类的文件,只要文件适合内存)。
comm + awk解决方案:
复杂的示例文件:
1.txt :
1. line-1 with spaces ( | | here 1.line-2 1.line-4 with tabs > 1.line-6 2.line-2 3.line-5 (tabs)
2.txt :
1.line-3 2.line-1 with spaces 2.line-2 2.line-4 2.line-6 with tabs 3.line-5 (tabs)
工作:
comm -12 1.txt 2.txt > file-common awk 'NR==FNR{ a[$0];next }!($0 in a){ print $0 > "file"ARGIND-1 }' file-common 1.txt 2.txt
comm -12 1.txt 2.txt > file-common
– 将通用行保存到file-common
文件
awk ...
– 会将1.txt
和2.txt
独有的行分别打印到文件file1
和file2
查看结果:
head file* ==> file1 <== 1. line-1 with spaces ( | | here 1.line-2 1.line-4 with tabs > 1.line-6 ==> file2 <== 1.line-3 2.line-1 with spaces 2.line-4 2.line-6 with tabs ==> file-common <== 2.line-2 3.line-5 (tabs)