Reguläre Ausdrücke in Perl (3)

## Zeichenwiederholung

Besonders flexibel werden reguläre Ausdrücke durch die Möglichkeit, nach dem mehrfachen Auftreten von Zeichen zu suchen (wobei "mehrfach" auch "keinmal" einschließt).

Dies erfolgt im allgemeinsten Falle durch die Angabe eines Zahlenbereichs in geschweiften Klammern. So bedeutet beispielsweise "`{2,5}`", daß das vorangehende Symbol 2,3,4 oder 5-mal auftreten darf.

Ein Beispiel:

    #!/usr/local/bin/perl -w

    $t = "A----Z";

    if($t =~ /-{1,3}/)  { print "true ($&)\n" } else { print "false\n" }  # Test 1
    if($t =~ /-{2,6}/)  { print "true ($&)\n" } else { print "false\n" }  # Test 2
    if($t =~ /-{5,10}/) { print "true ($&)\n" } else { print "false\n" }  # Test 3

Test 1 gibt den Wert "`true`" aus. Hier ist das gefundene Muster (in der Variablen `$&`) von besonderem Interesse: es werden drei Minuszeichen ausgegeben. Darin erkennt man eine wichtige Eigenschaft der Suche nach mehrfachen Zeichen: Perl versucht, möglichst viele davon zu finden. Dies sehen wir auch im zweiten Test: hier wird die Maximalzahl von vier Minuszeichen als gefundener Substring ausgegeben. Dagegen liefert der Test 3 den Wert "`false`", da die Bedingung nicht einmal durch die kleinste Zahl (5) erfüllt werden kann.

Da solche Mehrfachsuchen sehr häufig in Perl benutzt werden, gibt es einige Abkürzungen, die die Schreibarbeit vereinfachen (und auch zur Übersichtlichkeit beitragen):

> Abkürzung | Entsprechung | Bedeutung
> --- | --- | ---
> `{`n`}` | `{`n`,`n`}` | genau n-mal
> `{`n`,}` |  | mindestens n-mal
> `?` | `{`0`,`1`}` | höchstens einmal
> `+` | `{`1`,}` | mindestens einmal
> `*` | `{`0`,}` | beliebig oft

Beispiele:

    #!/usr/local/bin/perl -w

    $t = "xxxAzz";

    if($t =~ /x{2}/)  { print "true ($&)\n" } else { print "false\n" }  # Test 1
    if($t =~ /x{2,}/) { print "true ($&)\n" } else { print "false\n" }  # Test 2
    if($t =~ /z+/)    { print "true ($&)\n" } else { print "false\n" }  # Test 3
    if($t =~ /B?/)    { print "true ($&)\n" } else { print "false\n" }  # Test 4
    if($t =~ /xAy*/)  { print "true ($&)\n" } else { print "false\n" }  # Test 5
    if($t =~ /x*z+/)  { print "true ($&)\n" } else { print "false\n" }  # Test 6

Die Variable `$t` besteht alle Tests erfolgreich; die folgende Liste zeigt, an welchen Stellen jeweils der reguläre Ausdruck paßt (Perl sucht hier immer die größtmögliche Lösung):

- Test 1: `xxxAzz` (genau zwei aufeinanderfolgende "`x`")
- Test 2: `xxxAzz` (mindestens zwei aufeinanderfolgende "`x`")
- Test 3: `xxxAzz` (mindestens ein "`z`")
- Test 4: `xxxAzz` ("`B`" wird 0-mal "gefunden" - daher auch hier "`true`")
- Test 5: `xxxAzz` ("`x`" gefolgt von "`A`" evt. gefolgt von "`y`")
- Test 6: `xxxAzz` (beliebig viele "`x`" gefolgt von mindestens einem "`z`")

Wie man in Test 5 sieht, bezieht sich ein Sonderzeichen zur Mehrfachsuche nur auf das direkt davor stehende Zeichen (im Beispiel also nur auf das 'y'), nicht aber auf andere Symbole weiter vorne.

Wenn gesagt wird, daß Perl die größtmögliche Lösung sucht, so ist dies eigentlich nicht ganz richtig; man sollte genauer sagen: die längste Lösung, die sich bei der Suche ab dem Startpunkt des vorgegebenen Strings ergibt. Eine Suche beginnt immer vor dem ersten Zeichen der zu durchsuchenden Zeichenkette. Wird, von dieser Startposition ausgehend, eine Lösung gefunden, so wird die Suche (erfolgreich) abgebrochen, auch wenn es vielleicht weiter hinten noch eine längere Lösung gäbe. Bei einem Fehlschlag beginnt eine erneute Suche ein Zeichen weiter hinten; dieses Verfahren wiederholt sich so lange, bis eine Lösung gefunden oder das Ende des Strings erreicht wird.

    #!/usr/local/bin/perl -w

    $t = "aa==aaaa";

    if($t =~ /a*/) { print "true ($&)\n" } else { print "false\n" }  # Test 1
    if($t =~ /=*/) { print "true ($&)\n" } else { print "false\n" }  # Test 2
    if($t =~ /=+/) { print "true ($&)\n" } else { print "false\n" }  # Test 3

In Test 1 bietet Perl als gefundenen Substring 'aa' ('aa==aaaa') an, obwohl die Lösung 'aaaa' ('aa==aaaa') länger wäre. Ein scheinbar paradoxes Verhalten zeigt Test 2: hier ist die Lösung leer! Die Erklärung dafür ist, daß von der Startposition ausgehend eine Lösung gefunden wurde (0-mal '=') and dann die Suche abgebrochen wird. Test 3 schließlich gibt '==' aus, da ja hier mindestens ein '=' gefunden werden muß.

Die Problematik, daß Perl immer versucht, eine möglichst große Lösung zu finden, wurde schon angesprochen. Dies ist bisweilen unerwünscht; daher gibt es seit Perl 5 auch eine Variante, um eine möglichst kurze Lösung zu suchen. Dies geschieht, indem einfach ein Fragezeichen angehängt wird. Die entsprechenden Kombinationen sehen dann so aus:

> `{`n`,`m`}?``` | `{`n`}?``` | `{`n`,}?```
> `??` | `+?` | `*?`

Ein Beispiel, das den Unterschied verdeutlicht:

    #!/usr/local/bin/perl -w

    $t = "mmmmm";

    if($t =~ /m+/)  { print "true ($&)\n" } else { print "false\n" }  # Test 1
    if($t =~ /m+?/) { print "true ($&)\n" } else { print "false\n" }  # Test 2
    if($t =~ /m*?/) { print "true ($&)\n" } else { print "false\n" }  # Test 3

Der erste Test gibt 'mmmmm' als (maximale) Lösung aus, während Test 2 nur 'm' (die minimale Lösung) liefert. Der dritte Test schließlich bietet als Lösung gar einen leeren String an (0-mal 'm').

Es sei hier noch erwähnt, daß beispielweise "`m*?`" keinesfalls immer automatisch null Zeichen bedeuten muß wie man nach dem obigen Beispiel vielleicht zunächst glauben mag.

    #!/usr/local/bin/perl -w

    $t = "AmmmAmmmA";

    if($t =~ /Am*?A/) { print "true ($&)\n" } else { print "false\n" }  # Test 1

    if($t =~ /A.+A/)  { print "true ($&)\n" } else { print "false\n" }  # Test 2
    if($t =~ /A.+?A/) { print "true ($&)\n" } else { print "false\n" }  # Test 3

Hier enthält der gefundene Substring im Test 1 drei 'm' ('AmmmAmmmA'). Lehrreich ist auch der Vergleich der beiden anderen Tests: maximale Lösung in Test 2 ('AmmmAmmmA') und minimale Lösung in Test 3 ('AmmmAmmmA').

Autor: Eike Grote | Letzte Änderung: 03.11.2004