2012-04-15 1 views
2

Dies ist ein Follow-up von . Ich wollte zeigen, wie ich das Problem richtig lösen kann, aber ich habe ein unerwartetes Verhalten gezeigt.Wie kommt es, dass diese Regex nicht gierig ist?

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; 

Die zweite Übereinstimmung schlägt fehl. use re 'debugcolor' zeigt, dass :($port_re) bereits innerhalb der IPv6-Adresse :5 entspricht. Das überrascht mich, weil ich die Gier mit einem ? nicht ausgeschaltet habe. Ich habe damit gerechnet, dass alles bis auf die ] verschlungen wird, nur dann gegen den Trenndoppelpunkt und was danach folgt.

Warum passiert das, und was ist das Heilmittel?

Antwort

6

Gier nur ins Spiel kommen würde, wenn einer Ihrer Atome eine Wahl hat, wie viel es bieten kann (das heißt, wenn Sie verwendet *, +, ? oder {n,m}). Dies ist kein Problem mit der Gier.

Das Problem ist, dass die Regex nur eine IPv6-Adresse übereinstimmt, wenn unmittelbar gefolgt von "]" und ":". Das kann unmöglich passieren.

Sie könnten zwei verschiedene Spiele verwenden oder so etwas wie die folgenden verwenden:

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); 
} 

Vielleicht ist das ein bisschen saubere?

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}); 
} 
+0

Und noch eine verrückte Möglichkeit: Halte die Klammer im Lookahead und benutze '(? (2) \ 2)' nach dem Capture. Ich muss allerdings ein Listen-Slice für das Match-Ergebnis verwenden ('(...) [0,2]'). – ikegami

+0

Oh, wow, named Capture-Gruppen sind sicher hübsch! –

+0

@Richard Simões, Eingeführt in 5.10, IIRC, zusammen mit ein paar anderen Dingen. – ikegami

3

Zero-width-Assertions werden nicht konsumiert, sodass die wörtliche rechte Klammer immer noch vorhanden ist, um nach der ersten Capture-Gruppe abgeglichen zu werden. Diese Einstellung scheint zu funktionieren:

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

Ja, das habe ich fast geschrieben, aber ich fand es viel zu komplex; Es bedarf einer gründlichen Untersuchung, um festzustellen, was zutrifft, wenn Sie bereits wissen, was es tut. – ikegami