【问题标题】:Unix join two files with regular expressions using awkUnix 使用 awk 使用正则表达式连接两个文件
【发布时间】:2011-04-06 11:30:06
【问题描述】:

我有一个文件 (lookup.txt),其中包含一个查找表,其中包含一个正则表达式列表以及相应的数据(类别和句点)。例如

INTERNODE|household/bills/broadband|monthly
ORIGIN ENERGY|household/bills/electricity|quarterly
TELSTRA.*BILL|household/bills/phone|quarterly
OPTUS|household/bills/mobile|quarterly
SKYPE|household/bills/skype|non-periodic

我有另一个文件(data.txt),其中包含费用清单,例如:

2009-10-31,cc,-39.9,INTERNODE BROADBAND
2009-10-31,cc,-50,ORIGIN ENERGY 543546
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES
2009-10-31,cc,-90,TELSTRA MOBILE BILL
2009-11-02,cc,-320,TELSTRA HOME BILL
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN

我想将这两者结合在一起,从而使 data.txt 文件中的第 4 列与 lookup.txt 文件中第一列的正则表达式相匹配。

所以输出将是:

2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
2009-11-03,cc,-22.96,DICK SMITH
2009-11-03,cc,-251.24,BUNNINGS
2009-11-04,cc,-4.2,7-ELEVEN

我已经使用 bash 循环、循环查找、执行 greps 并在使用 sed 时添加额外的列来实现这一点,但它非常慢。所以想知道是否有更快的方法来做到这一点,比如使用 awk。

任何帮助将不胜感激。

【问题讨论】:

    标签: regex bash unix awk


    【解决方案1】:
    $ awk -F'|' 'FNR==NR{a[$1]=$2","$3;next}{m=split($0,b,",");for(i in a){if(b[4]~i){print $0","a[i];next}}}1' lookup file
    2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
    2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
    2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
    2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
    2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
    2009-11-03,cc,-22.96,DICK SMITH
    2009-11-03,cc,-251.24,BUNNINGS
    2009-11-04,cc,-4.2,7-ELEVEN
    

    【讨论】:

    • 完美。正是我想要的。与直接 bash 循环相比,速度非常快。
    • 我有一个类似的任务要做,所以如果你能解释一下这条 awk 行,我将不胜感激。
    【解决方案2】:

    你可以在 Python 中做到这一点:

    #!/usr/bin/python
    import csv, re
    lookup = []
    with open('lookup.txt') as f:
        for rec in csv.reader(f, delimiter='|'):
            lookup.append((re.compile(rec[0]), rec[1:]))
    with open('data.txt') as f:
        for rec in csv.reader(f, delimiter=','):
            for rexp, fields in lookup:
                if rexp.match(rec[3]):
                    rec.extend(fields)
                    break
            print ','.join(rec)
    

    对于您的文件lookup.txtdata.txt,它会在不到 0.3 秒的时间内返回以下内容:

    2009-10-31,cc,-39.9,INTERNODE BROADBAND,household/bills/broadband,monthly
    2009-10-31,cc,-50,ORIGIN ENERGY 543546,household/bills/electricity,quarterly
    2009-10-31,cc,-68,INTERNODE BROADBAND EXCESS CHARGES,household/bills/broadband,monthly
    2009-10-31,cc,-90,TELSTRA MOBILE BILL,household/bills/phone,quarterly
    2009-11-02,cc,-320,TELSTRA HOME BILL,household/bills/phone,quarterly
    2009-11-03,cc,-22.96,DICK SMITH
    2009-11-03,cc,-251.24,BUNNINGS
    2009-11-04,cc,-4.2,7-ELEVEN
    

    【讨论】:

      【解决方案3】:

      如果您没有正则表达式,您可以使用joinlookup.txt 有多少个正则表达式?如果只是那个,只需扩展它并删除该功能。

      【讨论】:

      • 我同意,我真的不需要正则表达式,但我必须提供一些情况,其中 lookup.txt 中的文本是 data.txt 中文本的一部分。例如,如果查找包含“TELSTRA”并且数据包含“TELSTRA MOBILE”,则它们必须匹配。所以不使用正则表达式,而是部分文字匹配。 unix join 是否适用于列的部分匹配?
      • 我看过了。显然不是。但我的建议是在lookup.txt 中放置两个条目。一个用于“TELSTRA MOBILE”,一个用于“TELSTRA HOME”。如果你只有十几个条目,这可能比重新发明join 更简单。
      【解决方案4】:

      Awk 的设计目的是一次处理一条记录的单个数据流,因此它不适合这项工作。这将是一个十分钟的 Perl 或其他更面向通用编程的语言的练习。

      如果您一心想在 awk 中完成所有操作,请编写一个脚本,从处理数据的查找文件中生成第二个 awk 脚本,然后运行第二个脚本。

      【讨论】:

      • 感谢您的回答。你知道任何你以前见过的很好的参考资料吗?即使用一个 awk 脚本编写第二个 awk 脚本?
      • 不能说我以前见过它,但我以前做过生成其他脚本的脚本。编写一个 awk 脚本,为查找文件中的几行生成您想要的输出,然后在确定它有效之后,编写一个脚本来生成您刚刚为所有这些行编写的内容。
      【解决方案5】:

      你可以在 Perl 中做到这一点。 Perl(或 Python)的优点是它们有处理 CSV 文件的库。您的示例很简单,但是如果双引号内有逗号会怎样?或者 utf8 呢?等等

      用于此的标准 Perl 库是 Text:CSV_XS。但是,它有点冗长,我更喜欢 Parse::CSV,它是 Text::CSV_XS 的包装器。

      #!/usr/bin/perl
      
      use strict;
      use warnings;
      use Parse::CSV;
      
      my %lookup;
      my $l = Parse::CSV->new(file => "lookup.txt", sep_char => '|');
      while (my $row = $l->fetch) {
         my $key = qr/$row->[0]/;
         $lookup{$key} = [$row->[1,]];
      }
      
      my $d = Parse::CSV->new(file => "data.txt");
      while (my $row = $d->fetch) {
         foreach my $regex (keys %lookup) {
            if ($row->[3] =~ $regex) {
               push @$row, @{$lookup{$regex}};
               last;
            }
         }
         print join(",", @$row), "\n";
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-10-26
        • 1970-01-01
        • 1970-01-01
        • 2022-12-24
        • 2013-01-04
        • 2014-10-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多