Wie werden verschachtelte Erfassungsgruppen in regulären Ausdrücken nummeriert?

84

Gibt es ein definiertes Verhalten dafür, wie reguläre Ausdrücke mit dem Erfassungsverhalten verschachtelter Klammern umgehen sollen? Können Sie insbesondere davon ausgehen, dass verschiedene Engines die äußeren Klammern an der ersten Position und die verschachtelten Klammern an den nachfolgenden Positionen erfassen?

Betrachten Sie den folgenden PHP-Code (unter Verwendung von regulären PCRE-Ausdrücken)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

Der gesamte Ausdruck in Klammern wird zuerst erfasst (ich möchte testen), und dann werden die inneren Muster in Klammern erfasst ("wollen" und "bis"). Dies ist logisch sinnvoll, aber ich konnte sehen, dass ein ebenso logischer Fall gemacht wurde, bei dem zuerst die Unterklammern und dann das gesamte Muster erfasst wurden.

Ist dieses Verhalten "Capture the Whole Ding First" in Engines für reguläre Ausdrücke definiert oder hängt es vom Kontext des Musters und / oder vom Verhalten der Engine ab (PCRE unterscheidet sich von C # und Java) als etc.)?

Alan Storm
quelle
Wenn Sie wirklich an allen Regex-Geschmacksrichtungen interessiert sind, ist das "sprachunabhängige" Tag genau das, was Sie wollen. Es gibt viel zu viele Geschmacksrichtungen, um sie alle aufzulisten, und die meisten entsprechen keinem echten Standard (obwohl sie bei der Nummerierung von Erfassungsgruppen bemerkenswert konsistent sind).
Alan Moore
Auf die Gruppe kann mit $ 1, $ 2, $ 3 usw. zugegriffen werden. Wie greife ich auf die 10. Gruppe zu? Wird es 10 $ sein? Ich denke nicht, dass $ 10 funktionieren wird, da es als $ 1 gefolgt von 0 interpretiert wird. Bedeutet dies, dass wir nur maximal 9 Gruppen haben können? Wenn der Autor dies bitte als Teil der Frage aufnehmen kann, ist dies ein zentraler Ort, um alles über verschachtelte Gruppen in regulären Ausdrücken zu erfahren.
LionHeart

Antworten:

59

Von perlrequick

Wenn die Gruppierungen in einem regulären Ausdruck verschachtelt sind, erhält $ 1 die Gruppe mit der am weitesten links stehenden Klammer, $ 2 die nächste öffnende Klammer usw.

Vorsichtsmaßnahme : Ohne Klammer zum Öffnen einer nicht erfassten Gruppe (? =)

Aktualisieren

Ich benutze PCRE nicht viel, da ich normalerweise das Original benutze;), aber die Dokumente von PCRE zeigen dasselbe wie die von Perl:

UNTERPATTERN

2.Das Untermuster wird als Erfassungsuntermuster eingerichtet. Dies bedeutet, dass, wenn das gesamte Muster übereinstimmt, der Teil der Betreffzeichenfolge, der mit dem Untermuster übereinstimmt, über das ovectorArgument von an den Aufrufer zurückgegeben wird pcre_exec(). Öffnungsklammern werden von links nach rechts gezählt (beginnend mit 1), um die Nummer für die Erfassungsuntermuster zu erhalten.

Zum Beispiel, wenn die Zeichenfolge "der rote König" mit dem Muster übereinstimmt

the ((red|white) (king|queen))

Die erfassten Teilzeichenfolgen sind "roter König", "roter" und "König" und mit 1, 2 bzw. 3 nummeriert.

Wenn PCRE von der Perl-Regex-Kompatibilität abweicht, sollte das Akronym möglicherweise neu definiert werden - "Perl Cognate Regular Expressions", "Perl Comparable Regular Expressions" oder so. Oder veräußern Sie einfach die Buchstaben der Bedeutung.

daotoad
quelle
1
@ Sinan: Er verwendet PCRE in PHP, was "Perl-kompatible reguläre Ausdrücke" ist. Es sollte also genauso sein wie die direkte Verwendung von Perl
Pascal MARTIN
3
Pascal, PCRE begann als Versuch, ein Perl-kompatibles reguläres Ausdrucksset zu sein, aber in den letzten Jahren sind die beiden leicht auseinander gegangen. Immer noch sehr ähnlich, aber es gibt subtile Unterschiede in den erweiterten Funktionen. (Außerdem bin ich laut Frage an allen Plattformen interessiert)
Alan Storm
1
Eigentlich ist es Perl, der heutzutage den größten Teil des "Wegdriftens" macht, aber Sie haben Recht: "Perl-kompatibel" wechselt schnell von einer Fehlbezeichnung zu einer Nicht-Sequenzierung. : D
Alan Moore
1
@ Alan, Perl ist definitiv in Bewegung. P5.10 hat ein paar Dinge geändert, aber 6 wird sehr unterschiedlich sein. Das P muss mit ziemlicher Sicherheit als "Perl 5" interpretiert werden. PCRE ist ein großartiges Projekt, das ich nicht genug loben kann. Es war ein Glücksfall bei mehr als ein paar Projekten.
Daotoad
1
Ich habe dies unter dem ersten Zitat hinzugefügt. Vorsichtsmaßnahme : Ohne Klammer zum Öffnen einer Gruppe ohne Erfassung (? =). Ich habe nicht bemerkt, dass ich nicht angemeldet war, als ich es bearbeitet habe. Erst als ich diesen Kommentar hinzufügte, wurde ich zur Eingabe von Anmeldeinformationen aufgefordert. Also braucht es jetzt 1 weitere Person, um zu genehmigen!
JGFMK
17

Ja, das ist alles ziemlich gut definiert für alle Sprachen, die Sie interessieren:

  • Java - http://java.sun.com/javase/6/docs/api/java/util/regex/Pattern.html#cg
    "Erfassungsgruppen werden nummeriert, indem ihre öffnenden Klammern von links nach rechts gezählt werden. ... Gruppe Null steht immer für den gesamten Ausdruck. "
  • .Net - http://msdn.microsoft.com/en-us/library/bs2twtah(VS.71).aspx
    "Erfassungen mit () werden automatisch anhand der Reihenfolge der öffnenden Klammern nummeriert, beginnend mit einer. Die erste erfassen, Element Nummer Null erfassen, ist der Text, der mit dem gesamten Muster des regulären Ausdrucks übereinstimmt. ")
  • PHP (PCRE-Funktionen) - http://www.php.net/manual/en/function.preg-replace.php#function.preg-replace.parameters
    "\ 0 oder $ 0 bezieht sich auf den Text, der mit dem gesamten Muster übereinstimmt. Öffnende Klammern werden von links nach rechts gezählt (beginnend mit 1), um die Nummer des erfassenden Untermusters zu erhalten. " (Dies galt auch für die veralteten POSIX-Funktionen)
  • PCRE - http://www.pcre.org/pcre.txt
    Um Alan M hinzuzufügen, suchen Sie nach "Wie pcre_exec () erfasste Teilzeichenfolgen zurückgibt" und lesen Sie den folgenden fünften Absatz:

    Das erste Paar von ganzen Zahlen, Ovektor [0] und Ovektor [1], identifiziert die
    Teil der Betreffzeichenfolge, der mit dem gesamten Muster übereinstimmt. Der nächste
    Paar wird für das erste Erfassungsuntermuster verwendet und so weiter. Der Wert
    Von pcre_exec () zurückgegeben wird ein Paar mehr als das Paar mit der höchsten Nummer
    Wurde festgelegt. Wenn beispielsweise zwei Teilzeichenfolgen erfasst wurden, wird die
    Der zurückgegebene Wert ist 3. Wenn keine erfassenden Untermuster vorhanden sind, erfolgt die Rückgabe
    Der Wert einer erfolgreichen Übereinstimmung ist 1, was darauf hinweist, dass nur das erste Paar vorhanden ist
    von Offsets wurde gesetzt.
    
  • Perl ist anders - http://perldoc.perl.org/perlre.html#Capture-buffers
    $ 1, $ 2 usw. stimmen mit den Erfassungsgruppen überein, wie Sie es erwarten würden (dh durch das Auftreten einer öffnenden Klammer), jedoch gibt $ 0 den Programmnamen zurück, nicht die gesamte Abfragezeichenfolge - um dies zu erreichen, verwenden Sie stattdessen $ &.

Sie werden höchstwahrscheinlich ähnliche Ergebnisse für andere Sprachen (Python, Ruby und andere) finden.

Sie sagen, dass es ebenso logisch ist, zuerst die inneren Erfassungsgruppen aufzulisten, und Sie haben Recht - es ist nur eine Frage der Indizierung beim Schließen und nicht beim Öffnen von Parens. (wenn ich dich richtig verstehe). Dies zu tun ist jedoch weniger natürlich (zum Beispiel folgt es nicht der Konvention zur Leserichtung) und macht es daher schwieriger (wahrscheinlich nicht signifikant), durch Insektion zu bestimmen, welche Erfassungsgruppe bei einem bestimmten Ergebnisindex sein wird.

Es ist auch sinnvoll, die gesamte Übereinstimmungszeichenfolge auf Position 0 zu setzen - hauptsächlich aus Gründen der Konsistenz. Dadurch kann die gesamte übereinstimmende Zeichenfolge unabhängig von der Anzahl der Erfassungsgruppen von Regex zu Regex und unabhängig von der Anzahl der Erfassungsgruppen, die tatsächlich mit etwas übereinstimmen, auf demselben Index bleiben (Java reduziert beispielsweise die Länge des Arrays für übereinstimmende Gruppen für jede Erfassung Die Gruppe stimmt mit keinem Inhalt überein (denken Sie beispielsweise an "ein (. *) Muster"). Sie können jederzeit die Capture_Gruppenergebnisse [Capturing_Gruppenergebnisse_Länge - 2] überprüfen, dies lässt sich jedoch nicht gut in Perl-Sprachen übersetzen, die dynamisch Variablen erstellen ($ 1) , $ 2 usw.) (Perl ist natürlich ein schlechtes Beispiel, da es $ & für den übereinstimmenden Ausdruck verwendet, aber Sie haben die Idee :).

Alan Donnelly
quelle
1
Schöne Antwort. Aber wie wäre es auch mit einem Update für Python (2 & 3) :-)
JGFMK
Was ist mit JavaScript!?!
Mesqueeb
9

Jede Regex-Variante, die ich kenne, gruppiert Zahlen in der Reihenfolge, in der die ersten Klammern erscheinen. Dass äußere Gruppen vor ihren enthaltenen Untergruppen nummeriert werden, ist nur ein natürliches Ergebnis, keine explizite Richtlinie.

Wo es interessant wird, ist mit benannten Gruppen . In den meisten Fällen folgen sie der gleichen Politik der Nummerierung nach den relativen Positionen der Eltern - der Name ist lediglich ein Alias ​​für die Nummer. In .NET-Regexen werden die benannten Gruppen jedoch getrennt von nummerierten Gruppen nummeriert. Zum Beispiel:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

Tatsächlich ist die Nummer ein Alias ​​für den Namen . Die den benannten Gruppen zugewiesenen Nummern beginnen dort, wo die "echten" nummerierten Gruppen aufhören. Das mag wie eine bizarre Richtlinie erscheinen, aber es gibt einen guten Grund dafür: In .NET-Regexen können Sie denselben Gruppennamen mehrmals in einer Regex verwenden. Dies ermöglicht Regexe wie die aus diesem Thread zum Abgleichen von Gleitkommazahlen aus verschiedenen Gebietsschemas:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

Wenn es ein Tausendertrennzeichen gibt, wird es in der Gruppe "Tausend" gespeichert, unabhängig davon, welcher Teil der Regex mit ihm übereinstimmt. Ebenso wird das Dezimaltrennzeichen (falls vorhanden) immer in der Gruppe "Dezimal" gespeichert. Natürlich gibt es Möglichkeiten, die Trennzeichen ohne wiederverwendbare benannte Gruppen zu identifizieren und zu extrahieren, aber diese Methode ist so viel praktischer, dass ich denke, dass sie das seltsame Nummerierungsschema mehr als rechtfertigt.

Und dann gibt es noch Perl 5.10+, mit dem wir mehr Kontrolle über das Erfassen von Gruppen haben, als ich zu tun weiß. : D.

Alan Moore
quelle
4

Die Reihenfolge der Erfassung in der Reihenfolge des linken Parens ist auf allen Plattformen, auf denen ich gearbeitet habe, Standard (Perl, PHP, Ruby, Egrep).

Devin Ceartas
quelle
"Erfassen in der Reihenfolge des linken Parens" Danke dafür, es ist eine viel prägnantere Art, das Verhalten zu beschreiben.
Alan Storm
1
Sie können die Aufnahmen in Perl 5.10 und Perl 6 neu nummerieren.
Brad Gilbert