【问题标题】:perl regex too greedyperl 正则表达式太贪心
【发布时间】:2019-11-06 17:02:28
【问题描述】:

我查看了其他成员提出的类似问题,并从他们的询问中应用(或尝试应用)解决方案,但他们没有解决我的问题。我的模式匹配和分组太贪心,不会在第一个管道(|)处停止。如果我变得更具体,我认为可以,但我想弄清楚如何在管道的第一个实例处停止模式匹配?

这里有几行

09:30:00.063|IN:|8=FIX.4.2|9=206|35=D|34=5159|49=CLIENT|52=20191024-13:30:00.050|56=SERV|57=DEST|1=05033|11=ABZ5702|15=USD|21=1|38=2000|40=2|44=92.48|47=A|54=5|55=RC|60=20191024-13:30:00.050|111=0|114=N|336=X|5700=AP|9281=SOV|10=202
09:37:21.208|IN:|8=FIX.4.2|9=170|35=D|34=5184|49=CLIENT|52=20191024-13:37:21.206|56=SERV|57=ATXB|1=J5129|11=136404|15=USD|21=1|38=100|40=2|44=1.39|47=A|54=2|55=DIW|59=2|60=20191024-13:30:00.206|10=029

我希望我的 perl 脚本从上述数据中返回以下输出:

09:30:00.063|13:30:00.050|ABZ5702
09:37:21.208|13:37:21.206|136404

我尝试了所有这些以及其他几个版本,但无法让它产生上述输出:

    #$msg =~ s/([^|]*).*|52=([^|]*).*|11=([^|]*).*/$1|$2|$3/;
    $msg =~ s/(.+)\|??.*|52=([^|]*).*|11=([^|]*).*/$1|$2|$3/;
    #$msg =~ s/^([^|]*).??|52=([^|]*).??|11=([^|]*).*/$1|$2|$3/;
    #$msg =~ s/^([^\|??]*).*|52=([^\|??]*).*|11=([^\|??]*).*/$1|$2|$3/;
    #$msg =~ s/(.*\|??).*|52=(.+\|??).*|11=(.+\|??).*/one $1|two $2|three $3/;
    #$msg =~ s/(.*?|).*|52=(.*?|).*|11=(.*|?).*/$1|$2|$3/;
    #$msg =~ /(.*)|??.*|52=(.*)|??.*|11=(.*)|??.*/$1|$2|$3/;
    #$msg =~ s/|.*-[0-3][0-9]:/|/;

print "$msg\n";```

我知道给猫剥皮的方法不止一种,但在某些情况下我需要使用模式匹配方法。我怎样才能让它使用模式匹配产生预期的输出,它在第一个管道(|)处停止每个组?有人可以告诉我我做错了什么吗?

【问题讨论】:

    标签: perl regex-greedy fix-protocol


    【解决方案1】:

    试试这个: s/(.*?)\|.*\|52=([^|]*).*\|11=([^|]*).*/$1 $2 $3/;

    有几个管道分隔符需要转义。

    你需要查看非贪婪匹配https://docstore.mik.ua/orelly/perl/cookbook/ch06_16.htm

    第一个匹配组是(.*?),而不是(.*)。 ? 表示我们尽可能少地匹配。

    一般来说,为了在 perl 中解析 FIX,只要没有重复组,我建议先在 | 上拆分,然后创建标签值对的哈希。

    【讨论】:

    • 谢谢!那成功了!我可以发誓我尝试了一些非常相似的东西。也许我用了“?”在所有组中。我想我理解您从 FIX 消息创建哈希的建议,但我只关心两个时间戳和标签 11。我正在做的是比较两个时间戳,如果第一个大于某个阈值,则发送一个带有两个时间戳和标签 11 的警报。我对哈希的担忧是,如果数据集发生变化怎么办?我无法比较这两个哈希值,因为这两个时间戳可能最终位于不同的位置。
    • 为什么我们需要在第一组 (.*?) 而不是其他组 (*) 上减少贪心?
    • 这是因为你使用 [^|]*,它告诉它匹配尽可能多的非'|'字符尽可能。但它只能做到这一点直到下一个'|'所以你在那里看不到任何贪婪的行为。
    【解决方案2】:

    我会做一些不同的事情 - 将行拆分为数组并处理数组的单个元素。

    如果行的格式预先确定并且永远不会改变,则正则表达式可能是一种特定情况下可接受的解决方案。

    use strict;
    use warnings;
    
    use Data::Dumper;
    
    my $debug = 0;
    
    while( my $line = <DATA> ) {
        my @array = split /\|/, $line;
    
        print Dumper(\@array) if $debug;
    
        $array[7]  =~ s/.+?-//;
        $array[11] =~ s/\d+=//;
    
        printf "%s\n", join '|', @array[0,7,11];
    
    }
    
    __DATA__
    09:30:00.063|IN:|8=FIX.4.2|9=206|35=D|34=5159|49=CLIENT|52=20191024-13:30:00.050|56=SERV|57=DEST|1=05033|11=ABZ5702|15=USD|21=1|38=2000|40=2|44=92.48|47=A|54=5|55=RC|60=20191024-13:30:00.050|111=0|114=N|336=X|5700=AP|9281=SOV|10=202
    09:37:21.208|IN:|8=FIX.4.2|9=170|35=D|34=5184|49=CLIENT|52=20191024-13:37:21.206|56=SERV|57=ATXB|1=J5129|11=136404|15=USD|21=1|38=100|40=2|44=1.39|47=A|54=2|55=DIW|59=2|60=20191024-13:30:00.206|10=029
    

    【讨论】:

    • 如果您指定每个字段的名称,那么 hash 会比数组更合适——使用 key=value 会更容易一对。在 array 的情况下,我有 计算 我需要解决的 array 的元素 来更改它。
    • 先解析(例如使用split)绝对是更好的方法。
    • 提示:您可以使用say(带use feature qw( say ); 或use 5.010;)或CORE::say(不带)代替printf "%s\n",
    • printf有什么问题吗?由于它不希望使用它?输出信息的方式有很多种。仍然感谢提示。
    • 不,say 比printf "%s\n", 更简单。这就是为什么它是一个提示而不是错误修复:) (printf $s 会很糟糕,因为它可能导致输出乱码,但这是另一回事。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    • 1970-01-01
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多