【问题标题】:How come this regex is not greedy?为什么这个正则表达式不贪心?
【发布时间】:2012-04-27 05:22:18
【问题描述】:

这是Perl regular expression to match an IP address 的后续报道。我想展示如何正确解决问题,但遇到了意想不到的行为。

use 5.010;
use strictures;
use Data::Munge qw(list2re);
use Regexp::IPv6 qw($IPv6_re);
use Regexp::Common qw(net);

our $port_re = list2re 0..65535;

sub ip_port_from_netloc {
    my ($sentence) = @_;
    return $sentence =~ /
        (                   # capture either
          (?<= \[ )
            $IPv6_re        #  IPv6 address without brackets
          (?=  \] )
        |                   # or
            $RE{net}{IPv4}  #  IPv4 address
        )
        :                   # colon sep. host from port
        ($port_re)          #   capture port
    /msx;
}

my ($ip, $port);
($ip, $port) = ip_port_from_netloc 'The netloc is 216.108.225.236:60099';
say $ip;
($ip, $port) = ip_port_from_netloc 'The netloc is [fe80::226:5eff:fe1e:dfbe]:60099';
say $ip;

第二次匹配失败。 use re 'debugcolor' 表明 :($port_re) 已经与 IPv6 地址中的 :5 匹配。这让我感到惊讶,因为我没有用? 关闭贪婪。我预计它会吞噬所有内容,直到],然后才匹配分隔冒号以及之后的内容。

为什么会发生这种情况,有什么补救措施?

【问题讨论】:

    标签: regex perl regex-greedy


    【解决方案1】:

    只有当你的一个原子可以选择匹配多少时,贪婪才会发挥作用(即,如果你使用*、+、? 或 {n,m})。这不是贪婪的问题。

    问题在于,如果 IPv6 地址紧跟在“]”和“:”之后,则该正则表达式只会匹配 IPv6 地址。这不可能发生。

    您可以使用两个不同的匹配项,或者您可以使用如下内容:

    my $port_re = list2re 0..65535;
    my $IPv4_re = $RE{net}{IPv4};
    
    sub ip_port_from_netloc {
        my ($sentence) = @_;
        return if $sentence !~ /
            (?: \[ ( $IPv6_re ) \]
            |      ( $IPv4_re )
            )
            : ($port_re)
        /msx;
    
        return ($1 // $2, $3);
    }
    

    也许这更干净一点?

    my $port_re = list2re 0..65535;
    my $IPv4_re = $RE{net}{IPv4};
    
    sub ip_port_from_netloc {
        my ($sentence) = @_;
        return if $sentence !~ /
            (?: \[ (?<addr> $IPv6_re ) \]
            |      (?<addr> $IPv4_re )
            )
            : (?<port> $port_re )
        /msx;
    
        return ( $+{addr}, $+{port} );
    }
    

    【讨论】:

    • 还有另一种古怪的可能性:在前瞻中捕获括号并在捕获后使用(?(2)\2)。不过,必须在匹配结果上使用列表切片 (( ... )[0,2])。
    • @Richard Simões,在 5.10 中引入,IIRC 以及其他一些东西。
    【解决方案2】:

    零宽度断言不会被消耗,因此文字右括号仍然存在以匹配跟随第一个捕获组。此调整似乎有效:

    /
        \[?(                   # capture either
          (?<= \[ )
            $IPv6_re        #  IPv6 address without brackets
          (?=  \] )
        |                   # or
            (?<! \[ )
            $RE{net}{IPv4}  #  IPv4 address
            (?! \] )
        )\]?
        :                   # colon sep. host from port
        ($port_re)          #   capture port
    /msx;
    

    【讨论】:

    • 是的,我差点把它贴出来,但我觉得太复杂了;如果您已经知道它的作用,则需要认真研究才能确定它匹配什么。
    猜你喜欢
    • 2014-02-25
    • 1970-01-01
    • 1970-01-01
    • 2012-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-12
    相关资源
    最近更新 更多