Das Problem: Ich codiere eine Bibliothek, die vom Benutzer bereitgestellten regulären Ausdruck erhält, der eine unbekannte Anzahl von Erfassungsgruppen enthält, die für andere Eingaben ausgeführt werden sollen, und möchte den Wert aller in einer Zeichenfolge verketteten Erfassungsgruppen extrahieren (zur weiteren Verarbeitung an anderer Stelle).

Es ist trivial, wenn die Anzahl der Erfassungsgruppen im Voraus bekannt ist, da ich sie nur spezifiziere:

#!/usr/bin/perl -w
my $input = `seq -s" " 100 200`;
my $user_regex = 
 qr/100(.*)103(.*)107(.*)109(.*)111(.*)113(.*)116(.*)120(.*)133(.*)140(.*)145/;

if ($input =~ $user_regex)  { print "$1 $2 $3 $4 $5 $6 $7 $8 $9 $10\n"; }

Korrekt erzeugt (ignorieren Sie das zusätzliche Leerzeichen):

 101 102   104 105 106   108   110   112   114 115   117 118 119 
 121 122 123 124 125 126 127 128 129 130 131 132   
 134 135 136 137 138 139   141 142 143 144

Wenn es jedoch mehr als 10 Erfassungsgruppen gibt, gehen Daten verloren, wenn ich den Code nicht ändere. Da die Anzahl der Erfassungsgruppen unbekannt ist, gehe ich derzeit mit Hunderten von manuell festgelegten Übereinstimmungen ("$ 1" bis "$ 200") unter no warnings Pragma und hoffe, dass es ausreicht, aber es scheint nicht besonders sauber zu sein oder robust.

Im Idealfall möchte ich etwas, das wie values %+ für benannte Erfassungsgruppen funktioniert, jedoch für nicht benannte Erfassungsgruppen. Ist es in Perl 5.24 möglich? Oder welchen weniger klobigen Ansatz würden Sie zum Abrufen von Inhalten aller nummerierten Erfassungsgruppen empfehlen?

3
Matija Nalis 19 Apr. 2018 im 16:34

5 Antworten

Beste Antwort

Vielleicht können Sie einfach in ein Array erfassen?

my @captured = $input =~ $user_regexp;
if( @captured ) { print join " ", @captured; print "\n"; }

Wenn Sie die nummerierten Erfassungsvariablen unbedingt verwenden müssen, verwenden Sie eval:

my $input = "abc";
my $re = qr/(.)(.)(.)/;
if( $input =~ $re){
  my $num = 1;
  print "captured \$$num = ". eval("\$$num") ."\n" and $num++
    while eval "defined \$$num";
}

Oder nur:

my $input = "abc";
my $re = qr/(.)(.)(.)/;
if( $input =~ $re){
  my $num = 1;
  print "captured \$$num = $$num\n" and $num++ while defined $$num;
}

... aber dieses letzte Beispiel mit skalaren Referenzen funktioniert nicht unter use strict.

4
revo 19 Apr. 2018 im 15:48

Wenn Sie Perl v5.26.2 (derzeit die neueste Version) oder höher ausführen, können Sie das integrierte Array @{^CAPTURE} verwenden, anstatt selbst auf die Erfassungsvariablen zuzugreifen

Wie bei einem normalen Array beträgt die Anzahl der Captures scalar @{^CAPTURE} und die Indizes reichen von Null bis $#{^CAPTURE}

Beachten Sie, dass das Array mit der letzten erfolgreichen Musterübereinstimmung gefüllt ist. Daher sollten Sie genau wie die Erfassungsvariablen selbst den Status einer Musterübereinstimmung überprüfen, bevor Sie den Inhalt von @{^CAPTURE} verwenden.

2
Borodin 19 Apr. 2018 im 14:44

Für Version 5.24 gibt es kein Array aller erfassten Werte, aber Sie können sie mithilfe der Start- / Endposition jeder Übereinstimmung extrahieren:

my $s  = <some string>;
my $re = <some regex with captures>;
my @matches;
if ($s =~ $re) {
    for my $i (0 .. $#-) {
        push @matches, substr($s, $-[$i], $+[$i] - $-[$i]);
    }
}
2
Michael Carman 19 Apr. 2018 im 15:03

Die von Michael Carman und Borodin werden in Perlvar hilfreich dokumentiert - http://perldoc.perl.org/perlvar.html#Variables-related-to-regular-expressions.

Trotzdem habe ich Ideen aus mehreren Beiträgen zu einer meiner Meinung nach umfassenderen Antwort zusammengefasst:

#!/usr/bin/env perl

use Modern::Perl;

my @a = 'abcde' =~ /(.).(.).(.)/;

say do { # map probably creates a temp anonymous array of capture strings
    no strict 'refs';
    join ' ', map { "$$_" } 1..$#-
};

say do { # no copy to array but eval
    eval '"' . join(" ", map { "\$$_" } 1..$#-) . '"';
};

say "@a"; # still not clear from OP why this wasn't the answer
0
mr_ron 19 Apr. 2018 im 15:27

Sie können die Zahlen in $ 1 $ 2 usw. als Variablen behandeln

$t="abcdefghijklmnop"; 
$t=~/(.)(.)(.)(.)(.)(.)(.)/; 
print $$_ for 1..10;

Sie können streng umgehen,

  use strict;
  $t="abcdefghijklmnop"; 
  $t=~/(.)(.)(.)(.)(.)(.)(.)/; 
{
    no strict;
    print $$_ for 1..10;
}

Sie können sie auch in ein Array einfügen (entnommen aus http://perldoc.perl.org/perlre). html)

use strict; 
my $t="abcdefghijklmnop"; 
my @a=$t=~/(.)(.)(.)(.)(.)(.)(.)/; 
print "@a";

Obwohl beide nicht perfekt sind, bedeutet die Verwendung strenger Referenzen, dass Sie die Namen Ihrer Variablen kennen. Daher kennen Sie im Idealfall Ihre Variablennamen, z. B. wie viele Erfassungsgruppen Sie verwendet haben

-1
hoffmeister 19 Apr. 2018 im 14:29