【问题标题】:Unix: find all lines having timestamps in both time series?Unix:在两个时间序列中找到所有具有时间戳的行?
【发布时间】:2015-01-13 21:07:15
【问题描述】:

我有时间序列数据,我想在其中找到所有相互匹配的行,但值可以不同(匹配到第一个选项卡)!您可以在下面看到 vimdiff,我想在其中删除仅在其他时间序列上出现的日期。

我正在寻找最简单的 unix 工具来执行此操作!

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

【问题讨论】:

  • 基本上就是这么简单:转置两个数据序列,在第一行找到唯一的条目,转回,从两者中删除唯一的日期——完成!最简单的工具是什么?
  • 我很困惑。你说你的输入是10-Apr-00 00:00 0 10-Apr-00 00:00 7,但你的截图显示了两个不同的文件
  • @AdamSmith file1: 10-Apr-00 00:00 0 and file2 10-Apr-00 00:00 7,现在清楚了吗?我在那里添加了一个分隔符 ||让它更清楚。
  • @shellter 排除因为 20-Apr 与 21-Apr 不匹配

标签: unix vim awk sed vimdiff


【解决方案1】:

让我们考虑这些示例输入文件:

$ 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,则将此行与file1 中的相应行一起打印出来。这两行用tab-||-tab隔开。

替代输出

如果你只想从file2中选择日期也在file1中的行,那么代码可以简化:

$ awk 'NR==FNR{a[$1]++;next;} {if ($1 in a) print;}' file1 file2
10-Apr-00       00:00   7

或者,更简单:

$ awk 'NR==FNR{a[$1]++;next;} ($1 in a)' file1 file2
10-Apr-00       00:00   7

【讨论】:

    【解决方案2】:

    应@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 中的行数。我没有做过任何分析,但如果时间复杂度是二次的,我不会感到惊讶。

    我的第二次尝试合并和排序这两个文件,然后扫描所有行以搜索对。这在线性时间内运行,因此速度要快 很多。请注意,此解决方案会破坏文件的原始顺序;字母排序不适用于此日期表示法。提供具有不同日期格式 (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 的解决方案非常快。字典的缺点是内存消耗。在巨大的输入文件上,我的解决方案应该有优势。

    【讨论】:

    • 优秀的答案!有没有其他工具可以更好地进行合并?我对大型输入文件特别感兴趣。
    • 有 join 命令,它使用公共键列合并来自不同文件的列,请参阅我的答案以获取问题情况下的示例。当可以获取一个包含两个输入文件合并的值的文件时,join 可能更适合。在我的回答中,我使用两个连续的连接来重建输入文件。我还没有测试过大文件的命令。
    • @Masi 存在许多替代方案,从导入数据库中的所有内容到在 Perl 或 C 中滚动您自己的解决方案。这取决于两件事。 (1) 输入文件。两个文件都很大,还是其中一个文件小到足以保存在内存中?文件是否已经排序(例如日志文件)?每一行是如何格式化的?相同的数据是否也可以在数据库中获得,或者通过一些日志分析工具获得? (2) 所需的输出。您是对整行感兴趣,还是只对共同部分(即日期)感兴趣?您的要求是否会发生变化?
    【解决方案3】:

    有一个相对不为人知的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
      

    使用的工具:cut、join、nl、sort。

    【讨论】:

    • 你能估计一下你的命令的效率吗?我认为您的方法就像一步合并。
    • 哇!对于大文件,这可能是最有效的解决方案;前提是文件是预先排序的(通常是日志文件)并格式化为yyyy-mm-dd...(这意味着您可以消除所有排序步骤;大文件的潜在瓶颈)。
    • @Masi 抱歉,我可以找到有关大文件性能的信息,但我没有查看源代码。由于 join 适用于预排序的文件,因此我认为 join 非常有效。这意味着一切都取决于输入文件的排序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    • 2013-03-23
    相关资源
    最近更新 更多