我有时间序列数据,我希望找到彼此匹配的所有行,但值可以不同(匹配到第一个标签)!你可以看到下面的vimdiff,我想摆脱只在其他时间序列中出现的日子。
我正在寻找最简单的unix工具来做到这一点!
简单示例
输入
Left file Right File
------------------------ ------------------------
10-Apr-00 00:00 0 || 10-Apr-00 00:00 7
20-Apr 00 00:00 7 || 21-Apr-00 00:00 3
输出
Left file Right File
------------------------ ------------------------
10-Apr-00 00:00 0 || 10-Apr-00 00:00 7
答案 0 :(得分:4)
让我们考虑一下这些示例输入文件:
$ cat file1
10-Apr-00 00:00 0
20-Apr-00 00:00 7
$ cat file2
10-Apr-00 00:00 7
21-Apr-00 00:00 3
将具有相同日期的那些行合并在一起:
$ awk 'NR==FNR{a[$1]=$0;next;} {if ($1 in a) print a[$1]"\t||\t"$0;}' file1 file2
10-Apr-00 00:00 0 || 10-Apr-00 00:00 7
NR==FNR{a[$1]=$0;next;}
NR
是到目前为止读取的行数,FNR
是到目前为止从当前文件读取的行数。所以,当NR==FNR
时,我们仍在阅读第一个文件。如果是这样,请在第一个字段$0
的键下的数组a
中保存整行$1
,这是日期。然后,跳过其余命令并跳转到next
行。
if ($1 in a) print a[$1]"\t||\t"$0
如果我们到达这里,那么我们正在阅读第二个文件file2
。如果此行中的第一个字段$1
是我们在file1
中看到的日期,换句话说,如果是$1 in a
,则将该行与来自{的相应行一起打印出来{1}}。这两行由制表符分隔 - file1
- 制表符。
如果您只想选择日期也在||
的{{1}}行,那么可以简化代码:
file2
或者,更简单:
file1
答案 1 :(得分:2)
有一个相对未知的unix命令 join 。它可以在键列上连接已排序的文件。
要在您的上下文中使用它,我们遵循此策略(left.txt和right.txt是您的文件):
添加行号(在最后一步中将所有内容放入原始序列中)
nl left.txt > left_with_lns.txt
nl right.txt > right_with_lns.txt
对日期列
上的两个文件进行排序sort left_with_lns.txt -k 2 > sl.txt
sort right_with_lns.txt -k 2 > sr.txt
使用日期列加入文件(所有时间都是0:00)(这会合并两个文件的所有列和相应的键,但是我们提供了一个输出模板来写入第一个文件中的列和第二个文件中的列位于其他位置(但只有那些带有匹配键的行将以结果fl.txt和fr.txt结尾)
join -j 2 -t $'\t' -o 1.1 1.2 1.3 1.4 sl.txt sr.txt > fl.txt
join -j 2 -t $'\t' -o 2.1 2.2 2.3 2.4 sl.txt sr.txt > fr.txt
在linenumber列上对两个结果进行排序并输出其他列
sort -n fl |cut -f 2- > left_filtered.txt
sort -n fr.txt | cut -f 2- > right_filtered.txt
使用的工具:cut,join,nl,sort。
答案 2 :(得分:1)
根据@Masi的要求,我尝试使用sed制定解决方案。
我的第一次尝试使用两次传球;第一个将file1
转换为第二个过程中使用的sed脚本,以过滤file2
。
sed 's/\([^ \t]*\).*/\/^\1\t\/p;t/' file1 > sed1
sed -nf sed1 file2 > out2
对于大输入文件,这是s-l-o-w;对于来自file2
的每一行,sed必须处理一定数量的模式,这些模式等于file1
中的行数。我没有做过任何剖析,但如果时间复杂度是二次的,我也不会感到惊讶。
我的第二次尝试合并并对两个文件进行排序,然后扫描所有行以搜索对。这在线性时间内运行,因此 lot 更快。请注意,此解决方案将破坏文件的原始顺序;使用此日期表示法,字母排序效果不佳。提供具有不同日期格式(y-m-d)的文件将是解决此问题的最简单方法。
sed 's/^[^ \t]\+/&@1/' file1 > marked1
sed 's/^[^ \t]\+/&@2/' file2 > marked2
sort marked1 marked2 > sorted
sed '$d;N;/^\([^ \t]\+\)@1.*\n\1@2/{s/\(.*\)\n\(.*\)/\2\n\1/;P};D' sorted > filtered
sed 's/^\([^ \t]\+\)@2/\1/' filtered > out2
说明:
s/^[^ \t]\+/&@1/
将@1
附加到每个日期。这样就可以合并文件,在排序时保持相同的日期,并且仍能分辨出不同文件中的行。file2
执行相同的操作;显然有自己的标记@2
。sort
命令合并这两个文件,将相同的日期分组在一起。file2
中所有日期同时出现在file1
中的行。@2
标记。第三个sed命令详细说明:
$d
禁止不当打印最后一行N
读取并将另一行输入附加到模式空间中已存在的行/^\([^ \t]\+\)@1.*\n\1@2/
匹配源自不同文件但具有相同日期的两行{
启动命令组s/\(.*\)\n\(.*\)/\2\n\1/
交换模式空间中的两行P
打印模式空间中的第一行}
结束命令组D
删除模式空间中的第一行坏消息是,即使是第二种方法也比@ John1024的awk方法慢。 Sed从未被设计成合并工具。两者都不是awk,但awk的优点是能够将整个文件存储在字典中,使得@ John1024的解决方案非常快。字典的缺点是内存消耗。在巨大的输入文件上,我的解决方案应该具有优势。