Unix:找到两个时间序列中都有时间戳的所有行?

时间:2014-11-17 02:09:11

标签: unix vim awk sed vimdiff

我有时间序列数据,我希望找到彼此匹配的所有行,但值可以不同(匹配到第一个标签)!你可以看到下面的vimdiff,我想摆脱只在其他时间序列中出现的日子。

我正在寻找最简单的unix工具来做到这一点!

enter image description here

enter image description here

Timeserie herehere

简单示例

  

输入

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

3 个答案:

答案 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是您的文件):

  1. 添加行号(在最后一步中将所有内容放入原始序列中)

    nl left.txt > left_with_lns.txt
    nl right.txt > right_with_lns.txt
    
  2. 对日期列

    上的两个文件进行排序
    sort left_with_lns.txt -k 2 > sl.txt
    sort right_with_lns.txt -k 2 > sr.txt
    
  3. 使用日期列加入文件(所有时间都是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
    
  4. 在linenumber列上对两个结果进行排序并输出其他列

    sort -n fl |cut -f 2- > left_filtered.txt
    sort -n fr.txt | cut -f 2- > right_filtered.txt
    
  5. 使用的工具: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命令合并这两个文件,将相同的日期分组在一起。
  • 第三个sed命令返回file2中所有日期同时出现在file1中的行。
  • 第四个sed命令从输出中删除@2标记。

第三个sed命令详细说明:

  • $d禁止不当打印最后一行
  • N读取并将另一行输入附加到模式空间中已存在的行
  • /^\([^ \t]\+\)@1.*\n\1@2/匹配源自不同文件但具有相同日期的两行
  • {启动命令组
  • s/\(.*\)\n\(.*\)/\2\n\1/交换模式空间中的两行
  • P打印模式空间中的第一行
  • }结束命令组
  • D删除模式空间中的第一行

坏消息是,即使是第二种方法也比@ John1024的awk方法慢。 Sed从未被设计成合并工具。两者都不是awk,但awk的优点是能够将整个文件存储在字典中,使得@ John1024的解决方案非常快。字典的缺点是内存消耗。在巨大的输入文件上,我的解决方案应该具有优势。