使用的基本解决方案sed
依赖于comm
仅在第一个文件中找到的不带前缀的输出行;它使用单个选项卡输出仅在第二个文件中找到的行;它使用两个选项卡输出在两个文件中找到的行。
它还依赖sed
'sw
命令来写入文件。
给定文件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
;BSDsed
不识别\t
转义符。显然,可以用实际的制表符代替\t
符号来编写文件,然后 BSDsed
就可以使用脚本了。
可以让它在命令行上运行,但它很繁琐(这是礼貌的)。使用 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 的进程替换在文件中的行之前添加前缀,然后后处理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
(并且可以使用未排序的文件,只要文件适合内存)。