【问题标题】:How to get the output from the comm command into 3 separate files?如何将 comm 命令的输出转换为 3 个单独的文件?
【发布时间】:2017-09-21 05:55:11
【问题描述】:

问题Unix command to find lines common in two files 有一个answer 建议使用comm 命令来完成任务:

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

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

comment 中,bapors 询问:

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

为了澄清,我问:

如果您希望仅在一个文件中的 File1 中的行,仅在另一个文件中的 File2 中的行,以及在第三个中的行,那么(假设文件中的行都没有以制表符开头)您可以使用 @ 987654329@ 将输出拆分为三个文件。

用户bapors确认:

这正是我要问的。你能举个例子吗?

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

【问题讨论】:

    标签: unix sed comm


    【解决方案1】:

    使用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 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(并且可以使用未排序的文件,前提是文件适合内存)。

    【讨论】:

    • 很棒的贡献。希望我们还有 SO 文档。为 BSD sed 的用户添加的一件事。如果您碰巧在 FreeBSD 中,您的 /bin/sh 虽然不是 bash 的 Almquist shell,但包含类似于 bash 的 C 样式引用。
    • 如果我在行的开头(和单词之间)添加多个制表符/空格,这似乎不起作用
    • @RomanPerekhrest:您是说使用script3.sed 的变体和进程替换在数据具有前导制表符或行内有多个空格或制表符时不起作用?如果是这样,我想查看示例数据。请您通过电子邮件将其发送给我 - 请参阅我的个人资料。一个可能的问题是sortcomm 对数据按排序顺序意味着什么看法不一致。您可能需要在环境中设置 LANG=C 才能使其正常工作。
    • @JonathanLeffler,请参阅此屏幕截图ibb.co/cCScW5。在顶部窗口 - 您的 sed 脚本内容,在底部窗口 - 语言环境、2 个文件内容和最终命令结果
    • 请注意,您使用的comm 版本正确报告file 1file 2 均未按排序顺序(错误消息输出)。 comm 的输入必须进行排序。如果输入没有排序,那么你得到你得到的。 comm 使用的算法是合并的变体;它随机播放每个文件,确定行是否相等,或者哪个在前,并生成适当的输出。如果输入未排序,则输出不可靠。我假设您也用制表符替换了脚本中的相关空格。
    【解决方案2】:

    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 &gt; 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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-01
      • 2018-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多