问题Unix command to find lines common in two files有answer建议使用comm
命令执行此任务:
comm -12 1.sorted.txt 2.sorted.txt
这显示了两个文件共有的行(-1
抑制仅在第一个文件中的行,而-2
仅抑制第二个文件中的行,只留下行这两个文件共同作为输出)。正如文件名所示,输入文件必须按排序顺序。
如何将输出放在不同的文件中?
寻求澄清,我问道:
如果你只希望文件中的行在一个文件中,那些只在另一个文件中的文件中,而在三分之一中的那些文件中,那么(如果文件中的所有行都没有以标签开头),你可以使用{ {1}}将输出拆分为三个文件。
用户bassors确认:
这正是我所要求的。你会举个例子吗?
答案相对冗长,会破坏另一个问题答案的简单性(用大量信息淹没),所以我在这里单独提出问题 - 并提供了答案。
答案 0 :(得分:2)
使用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
$
脚本的工作原理是:
file.3
,然后删除该行(以便忽略该脚本的其余部分),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
(如果文件适合内存,则可以使用未排序的文件)。
答案 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.txt
和2.txt
独有的行分别打印到文件file1
和file2
查看结果:
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)