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

时间:2017-09-21 05:55:12

标签: unix sed comm

问题Unix command to find lines common in two filesanswer建议使用comm命令执行此任务:

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

这显示了两个文件共有的行(-1抑制仅在第一个文件中的行,而-2仅抑制第二个文件中的行,只留下行这两个文件共同作为输出)。正如文件名所示,输入文件必须按排序顺序。

在该问题的comment中,bapors会问:

  

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

寻求澄清,我问道:

  

如果你只希望文件中的行在一个文件中,那些只在另一个文件中的文件中,而在三分之一中的那些文件中,那么(如果文件中的所有行都没有以标签开头),你可以使用{ {1}}将输出拆分为三个文件。

用户bassors确认:

  

这正是我所要求的。你会举个例子吗?

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

2 个答案:

答案 0 :(得分:2)

使用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,然后删除该行。
  4. 步骤3中的匹配和删除操作更多的是对称性而不是其他任何操作;它们可以省略(仅留下w file.1),这个脚本也可以这样做。但是,请参阅下面的script3.sed以了解保持对称性的进一步理由。

    如上所述,这需要GNU sed; BSD sed无法识别\t转义符。显然,该文件可以使用实际选项卡代替\t表示法编写,然后BSD sed可以使用脚本。

    可以在命令行中使它全部工作,但它很繁琐(并且对此有礼貌)。使用Bash的ANSI C Quoting,你可以写:

    $ comm 1.sorted.txt 2.sorted.txt |
    > sed -e $'/^\t\t/  { s///\n w file.3\n d\n }' \
    >     -e $'/^\t/    { s///\n w file.2\n d\n }' \
    >     -e $'/^[^\t]/ {        w file.1\n d\n }'
    $
    

    script.sed的三个“段落”中的每一个写入单独的-e选项中。 w命令很挑剔;它需要文件名,只有文件名,在脚本的同一行之后,因此在脚本中的文件名之后使用\n。有很多空间可以消除,但对称性更清晰,显示的布局。使用-f script.sed文件可能更简单 - 它肯定是一种值得了解的技术,因为它可以避免在sed脚本必须在单引号,双引号和后引号上运行时出现问题,这使得编写起来很困难Bash命令行上的脚本。

    最后,如果这两个文件可以包含以制表符开头的行,则此技术需要更强大的力才能使其正常工作。一个变体解决方案利用Bash的process substitution在文件中的行之前添加前缀,然后后处理sed脚本在写入输出文件之前删除前缀。

    script3.sed(标签最多替换为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(如果文件适合内存,则可以使用未排序的文件)。

答案 1 :(得分:0)

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)