unix - 如何将 comm 命令的输出放入 3 个单独的文件中?

标签 unix sed comm

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

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

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

comment中对于这个问题,bapors问:

How would one have the outputs in different files?

为了寻求澄清,我问:

If you want the lines only in File1 in one file, those only in File2 in another, and those in both in a third, then (provided that none of the lines in the files starts with a tab) you could use sed to split the output to three files.

用户bapors已确认:

It is exactly what I was asking. Would you show an example?

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

最佳答案

使用 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
$

该脚本的工作原理是:

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

关于unix - 如何将 comm 命令的输出放入 3 个单独的文件中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46336404/

相关文章:

bash - 如果 "grep -f file"具有空分隔项,如何使用 "file"?

javax.comm.portinuseexception 当前拥有的端口

linux - 使用 expr 将单个整数参数加倍

linux - 控制寻呼机是否被任何程序使用的标准方法是什么(能够不使用寻呼机)?

xml - 使用 sed 创建 XML 数组

parsing - 为文件中的匹配行添加前缀

linux - x loader Makefile 中的 sed 用法

linux - grep 在两个文件中返回两列

bash - SSH 不从命令行退出

android - 系统重新启动后环境路径设置为 "PATH"变量丢失?