In diesem Abschnitt behandeln wir "Regular-Expressions", den in PERL eingebauten Apparat zur Textanalyse und Textbearbeitung. Wir werden dabei betrachten:
Einer der prominentesten Einsatzbereich von PERL ist das Suchen oder Ersetzen von komplexen Textmustern, den sogenannten "regular Expressions", im Folgenden nur noch mit "RE" abgekürzt. Lange vor der Erfindung von PERL wurden REs schon in vielen Unix-Tools ( z.B: grep bzw. egrep,sed und awk ) und Editoren ( z.B: vi,emacs) verwendet. Die Legende der Entstehung von PERL besagt sogar, dass PERL im wesentlichen deswegen geschrieben wurde weil das AWK von 1986 nicht in der Lage war mehrere Dateien aufgrund von gerade eingelesener Information zu öffnen oder zu schließen.
Eine Standard-PERL Installation bringt die beiden von Larry Wall geschriebenen Dienstprogramme a2p und s2p mit, mit denen awk bzw sed Scripte in kompatible PERL-Scripte konvertiert werden können.
Die einfachste Anwendung der REs ist das Suchen nach bestimmten Ausdrücken innerhalb von Strings. Dazu dient der Match Operator:
m/<expr>/[<options>]
Das führende m dient als Bezeichnung für den match-Operator, expr ist ein RE, und die Liste der optionalen Modifizierer options dient dazu, die Interpretation eines REs zu modifizieren. Die einfachste RE ist eine einfache Zeichenkette, ein oft gebrauchter Modifizierer ist der Modifizierer i, der bewirkt das die Groß/Kleinschreibung bei der Interpretation des REs ignoriert wird.Das "i" steht für ignorecase.
Beispiel :
$tier="KUH";
if ( $tier =~ m/kuh/i ){
print "eine Kuh !";
}
else {
print "keine Kuh !"
}
Hier wird die Zeichenkette "KUH"
mit dem RE kuh verglichen. Da der
ignorecase-Modifizierer die Unterscheidung von Groß--
und Kleinbuchstaben abstellt, liefert der
Ausdruck ("KUH= m/kuh/i") einen Treffer.
Anstelle des Operators =~
kann auch der Operator != verwendet werden, der
die Negation von =~ zurückliefert.
Einfache Zeichenketten wie im vorangegangenen Beispiel rechtfertigen natürlich keinen so aufwendigen Apparat wie REs. Interessantere REs lassen sich durch sogenannte Wildcards bilden die den Wildcards in der Shell-Programmierung, vergleichbar sind, und als Abkürzung für ein oder mehrere spezielle Zeichen dienen:
Wildcards:
"word": Trifft auf alle Ziffern 0,1,...9, alle Gross- und Kleinbuchstaben, sowie den underscore "_" zu.
"non word": Trifft auf alle anderen als die oben angegebenen Zeichen zu.
"whitespace character": Die Zeichen Newline (\n), Carriage Return (\r), Tabulator (\t) und Formfeed (\f)
"non-whitespace character". (Alles was kein Whitespace ist)
Trifft auf jedes Zeichen zu (auch Whitespaces). Will man den Punkt (period) als simples ASCII Zeichen verwenden, dann muss man ihn mit einem Backslash ausmaskieren.
"digit character". Die Ziffern 0,1,2,...9
"non-digit character" Alles was keine Ziffer ist.
Mit diesen Wildcards lassen sich schon interessantere REs bilden:
/\d\d\d/
bezeichnet eine dreistellige Dezimalzahl, (möglicherweise mit mehreren führenden Nullen)
/\d\.\d\d/
eine Dezimalzahl mit einer Vor-- und zwei Nachkommastellen.
Wie normale Zeichenketten können REs auch Sonderzeichen enthalten, die dann durch einen vorangestellen Backslash markiert werden müssen:
Alarm (Bell, ASCII #7)
newline Zeilenvorschub
Wagenrücklauf ;) (carriage-return)
Tabulator (tab)
form-feed, Seitenvorschub
Escape
Das Beispiel mit den Dezimalzahlen wirft die Frage auf, wie man bestimmte Zeichengruppen mit nicht a priori bekannter Anzahl von Zeichen erkennen kann. Ein Beispiel für ein solches Problem ist das Erkennen von Dezimalzahlen, die folgender Definition genügen sollen:
Eine Dezimalzahl ist eine Folge von Ziffern, optional gefolgt von einem Komma sowie einer Folge von weiteren Ziffern.
Durch das Hintenanstellen eines sogenannten "Quantifizierers" lässt sich angeben, wie
oft ein Zeichen (oder Ausdruck) wiederholt werden darf. Die einfachste Form eines Quantifizierers
ist die Angabe eines Bereiches in geschweiften Klammern. Die Angabe
{x,y} bedeutet, dass der vorangestellte Ausdruck mindestens x-mal
aber höchstens y-mal vorkommen darf. So steht der Ausdruck
\d{1,8}// für eine Ziffernfolge, die zwischen einer und 8 Ziffer enthält,
der Ausdruck \d{5,5}// für eine Ziffernfolge mit genau fünf Ziffern,
und der Ausdruck /\d{0,0}/ für "keine Ziffer".
Um beliebig lange Buchstabengruppen zuzulassen, kann die "rechte", obere Grenze
eines Bereichsquantifizierer auch weggelassen werden.
{n,} steht also für mindestens n und Wiederholungen.
Die Obergrenze "m"
unterliegt übrigens einer Beschränkung, es muss
m < 2^16 == 65536 sein.
Aus historischen und Gründen der Schreibfaulheit gibt es auch noch die
Quantifizierer {n},?,+ und * die eine Entsprechung in der
Klammerschreibweise haben :
genau n-mal, entspricht {n,n}
höchstens einmal, entspricht {0,1}
mindestens einmal, entspricht {1,}
beliebig oft, auch keinmal, entspricht {0,}
Auch mit den bis jetzt erarbeiteten Werkzeugen besteht noch keine Möglichkeit eine "ordentliche" Dezimalzahl von einer wirren Ziffernfolge zu unterscheiden. Z.B. sollte eine ordentliche Dezimalzahl nur dann ein Komma enthalten, wenn dieses Komma auch von einer Folge weiterer Ziffern gefolgt wird. So ist die Zeichenfolge "123." in unserem Sinne keine ordentliche Dezimalzahl.
REs lassen sich mit runden Klammern gruppieren. Auf diese Weise kann man REs innerhalb von REs bilden. Quantifizierer die hinter solchen Gruppen stehen beziehen sich auf die ganze Gruppe, nicht nur auf das letzte Zeichen:
$test=1212;
if ($test =~ m/(12){2,}/ ){ print "mehr als eine 12-Folge";}
Ein weiterer Effekt der Klammerung ist es, dass die Treffer einer Gruppe in einem Feld $n (n=1,2,3,4....) der sogenannten Rückwärtsreferenz gespeichert werden, und dadurch nach, oder sogar schon während der Auswertung der RE zur Verfügung stehen. Ein Treffer der ersten Klammer wird in das Feld $1 geschrieben, ein Treffer der zweiten Klammrr nach $2,...usw. Das folgende Script zerlegt eine als Kommandozeilenargument übergebene Dezimalzahl, und liefert die Ziffern vor dem Komma, das Komma und die Nachkommaziffern in der entsprechenden Rückwärtsreferenz zurück:
#!/usr/bin/perl
$input=@ARGV[0];
print "\n \n $input : ";
$input=@ARGV[0];
$input =~ m/(\d+)(\.)(\d*)/ ;
print"\n Vorkommaziffern : $1";
print"\n Komma : $2";
print"\n Nachkommaziffern : $3";
Die Auswertung der Rückwärtsreferenzen kann auch schon innerhalb des regulären Ausdruckes selbst erfolgen. Innerhalb eines RE lässt sich der Inhalt von $1 mit dem Ausdruck \1 der Inhalt von $2 mit demAusdruck \2,...usw referenzieren. Das folgende Script prüft, ob das erste "nichtweiße" Zeichen des Kommandozeilenargumentes mehr als einmal im gesammten Wort vorkommt:
#!/usr/bin/perl
$input=$ARGV[0];
if( $input =~ m/(\S).*(\1).*/){
print "$1 kommt mehrfach vor";
}
Die Zuweisung von Treffern an die Felder $1,$2,... lässt sich auch
unterdrücken: Dazu muss direkt nach der öffnenden Klammer
die Sequenz ?: eingefügt werden:
if("test" =~ m/(?:t)/){
print "Wert von \$1 : $1 \n";
}
Liefert die Ausgabe :
Wert von $1 :
Während
if("test" =~ m/(t)/){
print "Wert von \$1 : $1 \n";
}
die Ausgabe
Wert von $1 : t
liefert.
Die Verwendung von Gruppen und Wildcards kann dazu führen, das Treffer einer Gruppe in einem String nicht eindeutig sind. Im folgenden Codesnippet (greedy.pl) wird versucht, einen Teststring in zwei Anteile zu zerlegen, die durch einen Punkt getrennt sind:
#!/usr/bin/perl
$test="1.2.3";
print "\n\n greedy :\n";
$test =~ m/(.*)\.(.*)/;
print "\n \$1 : $1 ";
print "\n \$2 : $2 ";
Das Problem in obigem Beispiel liegt darin, dass der Teststring zwei Punkte enthält an denen er zerlegt werden kann. Per Voreinstellung verhält sich PERL gierig (engl: greedy) und liefert den größtmöglichen Treffer (maximales Matching) zurück. Dementsprechend liefert obiges Script die Ausgabe:
greedy :
$1 : 1.2
$2 : 3
Wenn dieses Verhalten nicht erwünscht ist, dann kann man PERL anweisen nur ein minimales Matching zurückzuliefern, indem man wie im folgenden Beispiel den non-greedy Operator benutzt. Dies ist ein simples Fragezeichen. Hinter den Wiederholungsoperator gestellt bewirkt es, dass schon der erste (minimale) Treffer nach $x geschrieben, und dann gleich die Verarbeitung der restlichen REs fortgesetzt wird:
#!/usr/bin/perl
$test="1.2.3";
print "\n\n non-greedy :\n";
$test =~ m/(.*?)\.(.*)/;
print "\n \$1 : $1 ";
print "\n \$2 : $2 ";
liefert die Ausgabe:
non-greedy :
$1 : 1
$2 : 2.3
Der non-greedy Operator kann hinter allen Quantifizerern stehen:
( {n,m}?, {n,}?, *?, +?,
?? ) sind zulässige und sinvolle Ausdrücke.
Neben Zeichenkombinationen bilden Aussagen über die Position von Zeichenkombinationen innerhalb von Wörtern oder Zeilen (Zeilenanfang, Zeilenende, Wortanfang, Wortende,..) einen weitern wichtigen Bestandteil von REs. Die Positionsprüfungen für Zeilenanfang und Zeilenende ergeben verschiedene Ergebnisse, je nachdem welcher Modifizierer verwendet wird. Ohne den Modifizierer /m (Multiline) werden Zeilenumbrüche ignoriert:
$test=" \nZeile 2 ";
if ($test =~ m/^Z/m) { print "\n Treffer bei Multiline Option \n"; }
unless ($test =~ m/^Z/) { print "\n kein Treffer ohne Multiline Option \n"; }
Dieses Codesnippet sucht nach Zeilen die mit dem Buchstaben "Z" beginnen, und liefert nur ohne die Multiline Option einen Treffer.
Die folgenden Zeichen markieren die Position einer RE innerhalb einer Zeichenkette:
Anfang der Zeichenkette
Zeilenanfang (mit Modifizierer /m)
Ende der Zeichenkette
Zeilenende (mit Modifizierer /m)
Wortgrenze (zweichen \w und \W)
Anfang der Zeichenkette
Ende der Zeichenkette
Auch die Position einer Zeichenkette relativ zu einem weiteren, als RE qualifizierten Zeichenkette kann selber wieder Bestandteil einer RE sein. Dafür gibt es die Sogenannte "Lookahead-Behauptungen":
Prüft ob die in RE formulierte Behauptung als nächstes erkannt werden würde.
Prüft ob die in RE formulierte Behauptung als nächstes nicht erkannt werden würde.
Die namensgebende Besonderheit der Lookahead-Bedingung ist dabei, dass die in der Lookahead Bedingung formulierte Behauptung geprüft wird, ohne dass die Abarbeitung der RE entsprechend weitergesetzt wird !
$test="lookahead at me";
if($test =~ m/(.*)(?=at\sme)(at\sme)/ ){
print $1;
print $2;
}
In diesem Beispiel wird sich die bereits in (?=...) abgeprüfte Zeichenfolge "at me" nach der Verarbeitung der ersten Klammer ein zweites mal angesprungen und der Variablen $2 zugewiesen. Die Möglichkeit zur vorausschauenden Suche ist erst seit PERL 5 implementiert.
Die eckigen Klammern "[...]" dienen dazu ganze Listen von Zeichen zu qualifizieren. Eine solche Prüfung gilt als bestanden wenn eine in der Liste enthaltenes Zeichen gefunden wird:. Die einfachste Form von Listen sind einfache Aufzähllungslisten:
m/(\b[aB].*)/
prüft z.B, ob ein Wort mit einem kleinen oder einem großen B begint.
PERL erlaubt auch die Angabe ganzer Listen von Zeichen, die
als Ausschnitt aus der ASCII-Tabelle angegeben werden,
z.B [0-9] für alle Ziffern, [a-z] für alle Kleinbuchstaben.
In reinen Aufzählungslisten muss das Minuszeichen also eventuell mit einem
Backslash maskiert werden.
Ein vorangestelltes "caret" (^) invertiert die Aussage der
Zeichenliste:
[^abc]
wird von allen Zeichen außer den Kleinbuchstaben "a","b","c"
erfüllt.
"Ausgewachsene" REs, die mehr als nur ein Zeichen enthalten, werden alternativ getestet indem man eine
Liste von REs angibt die durch ein "Pipesign" "|"getrennt werden. Der Ausdruck:
m/(Tick|Trick|Track)\s*Duck/
prüft die vollen Namen der drei Neffen von Donald Duck.
Mit der PERL-spezifischen (?#...) Anweisung kann man sogar Kommentare innerhalb von REs unterbringen. Der Gesammte Inhalt der Klammer wird bei der Abarbeitung ignoriert:
s/suchmich(?#ich bin ein kommentar und werde ignoriert !)/
PERL unterstützt eine ganze Reihe von Modifizieren und Optionen mit
denen das Suchverhalten manipuliert werden kann. Meistens werden diese
Modifizierer hinter den letzten slash des m/.../-Operators gestellt und wirken dann global,
d.h: auf die ganzen RE. Es können auch mehrere Modifizierer angegeben werden,
die wildeste Kombination ist m/.../gimosx.
global globale Suche (findet alle Vorkommen)
ignorcase ignoriert Groß/Kleinschreibung
multiline behandelt Zeilen wie neue Strings
once Kompiliert ein Muster nur einmal. Wenn eine RE Variablen enthaält werden diese normalerweise vor jeder Suche neu evaluiert, was natürlich Zeit kostet. Die once-Option unterdrückt diese Neuevaluation, und spart damit Zeit, allerdings wird eine Veränderung des Variablenwertes zur Laufzeit dann nicht mehr bemerkt.
Unterdrückt Zeilenumbrüche
Verwende PERL-Spezifische Syntaxerweiterungen
Neben den schon mehrfach erwähnten Rückwärtsreferenzen werden noch eine ganze Reihe von weiteren speziellen Variablen unterstützt:
Die Rückwärtsreferenzen
gibt das gefundene Muster zurück
enthält die Zeichenkette, die vor dem gefundenen Muster steht
enthält die Zeichenkette, die hinter dem gefundenen Muster steht
enthält das Muster der letzten runden Klammer
Wie die Vorbilder sed und awk beherscht PERL natürlich auch
das Suchen und Ersetzen. Dazu wird der Substitutionsoperator s.../../.../
verwendet:
s/<searchexp>/<replaceexp>/<optionen>
Das "s" steht dabei für substitute.
Der Reguläre Ausdruck in der Suchliste wird dabei durch den Ausdruck in der Ersetzungsliste
ersetzt. Der s/../../ Operator unterstützt die gleich Modifizierer wie der
m/.../ Operator, sowie zusätzlich noch einen Modifizierer "e",
der die Substitutionen in der Ersetzungsliste beeinflusst. In den meisten
Anwendungsfällen wird man den Modifizierer g einsetzen, um jedes Vorkommen
des Suchstrings zu ersetzen, nicht nur das erste.
Als erstes Beispiel ersetzen wir in zwei Schritten die führenden und schließenden
Whitespaces in einem String, wobei im zweiten Schritt die schließenden Blanks durch
"nichts" ersetzt werden, die Ersetzungsliste folglich leer ist:
$input=" vorne und hinten Blanks ";
$input =~ s/^\s*(.*)/\1/;
print ">$input<";
$input =~ s/(\s*$)//;
print ">$input<";
Oft wird die originale Zeichenkette weiterhin benötigt, und der modifizierte Text soll einer anderen Variablen zugewiesen werden. Dies lässt sich folgendermaßen erreichen:
$original="Bill fuerchtet sich vor Maeusen";
($modifiziert = $original) =~ s/Maeus/Pinguin/;
print "\n original : $original \n";
print "\n modifiziert : $modifiziert \n\n\n";
Das Camelbook beschreibt die Implementation der Regulären Ausdrücke als nichtdeterministischen Automaten. Obwohl sich das zunächst abschreckend anhört ist diese Sicht auf PERL REs durchaus lesbar und verständlich, zudem ein Muss , wenn es darum geht große Datenmengen zu durchsuchen und dabei zeitraubendes Backtracking zu vermeiden.
Die "User Contributed PERL Documentation " bringt eine Syntaxreferenz für reguläre Ausdrücke. Diese Referenz hat den Vorteil praktisch überall da vorhanden zu sein, wo man ein PERL Environment vorfindet. Die Referenz lässt sich mit
perldoc perlre
oder
man 1 perlre
aufrufen.
Diese Syntaxreferenz beeinhaltet keine Anwendungsbeispiele. Online findet man Anwendungsbeispiele in der perlop-Hilfe (Perl Operators) in der Unterabteilung "Regexp Quote-Like Operators", mit den Kommandos:
perldoc perlop
oder
man 1 perlop
Die meisten Fagen zu REs wurden bereits einmal gestellt und beantwortet. Mit Glück befindet sich die Frage und die Antwort in der PERLFAQ, und lässt sich mit
perldoc perlfaq6
oder
man 1 perlfaq6
aufrufen. Hier befinden sich auch viele Antworten auf solche Fragen die man nicht zu fragen wagt.