如何将comm命令的输出转换为3个独立的文件?

Unix命令查找两个文件中常见的行的问题有一个答案,build议使用comm命令来执行任务:

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

这显示了两个文件共同的行( -1压制仅在第一个文件中的行, -2压缩仅在第二个文件中的行,只留下两个文件共同的行作为输出)。 如文件名称所示,input文件必须按sorting顺序。

在对这个问题的评论中, 水煤浆问道:

如何将输出在不同的文件?

为了澄清,我问:

如果你只想在一个文件中只有File1的行,那么只有在File2中的行在另一个文件中,而那些在第三个文件中的行,那么(如果文件中没有行以tab开始),你可以使用sed来分割输出到三个文件。

用户资料证实:

这正是我所问的。 你会举一个例子吗?

答案是相对冗长的,会破坏其他问题答案的简单性(淹没了大量的信息),所以我在这里单独提出了这个问题,并提供了一个答案。

使用sed的基本解决方案依赖于comm输出行只在第一个没有前缀的文件中找到; 它只输出第二个文件中只有一个标签的行; 并使用两个选项卡输出在两个文件中找到的行。

它也依靠sedw命令写入文件。

给定文件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 $ 

该脚本的工作原理是:

  1. 匹配以2个选项卡开始的行,删除选项卡,将行写入到file.3和删除行(因此忽略脚本的其余部分),
  2. 匹配以1个选项卡开始的行,删除选项卡,将行写入file.2 ,并删除行(因此脚本的其余部分将被忽略),
  3. 匹配不以标签开头的行,将行写入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.txt2.txt独有的行分别打印到文件file1file2


查看结果:

 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)