使用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
$
脚本的工作方式:
- 匹配以 2 个选项卡开头的行,删除选项卡,将行写入
file.3,然后删除该行(因此脚本的其余部分被忽略),
- 匹配以 1 个制表符开头的行,删除该制表符,将该行写入
file.2,然后删除该行(因此脚本的其余部分被忽略),
- 匹配不以制表符开头的行,将该行写入
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(并且可以使用未排序的文件,前提是文件适合内存)。