Eine der nützlichsten Eigenschaften von Perl (wenn nicht die nützlichste Eigenschaft) ist das mächtige String-Handling. Und der Motor des String-Handling sind die regulären Ausdrücke (RE oder auch RegEx für engl. regular expression) zur Mustererkennung.
Ein regulärer Ausdruck steht zwischen zwei Schrägstrichen /
und der Mustererkennungs-Operator ist =~
.
Der folgende Ausdruck ist true, falls der String (oder das Muster, oder der reguläre Ausdruck) das in der Variablen $satz
vorkommt.
$satz =~ /das/;
Effektiv ist der "generische Musteroperator" m//
. Die Schrägstriche können mit der m
-Notation durch ein beliebiges anderes Zeichen ersetzt werden.
$satz =~ m:das:;
Ohne m
-Notation braucht es die Schrägstriche.
Eine RegEx unterscheidet Groß- und Kleinschreibung, dh. mit
$satz = "Das Muster kommt vor!";
wird der obige Vergleich false
. Der Operator !~
macht das Gegenteil.
Mit obiger Zuweisung wird deshalb der Ausdruck
$satz !~ /das/
true
, da der String das in $satz
nicht auftritt.
$_
Wir können die folgende Bedingung verwenden
if ($satz =~ /uster/) {
print "Das Muster \'uster\' kommt vor.\n";
}
die eine Nachricht ausgibt, falls wir einen der folgenden Sätze hätten:
$satz = "Ein Muster ohne Wert";
$satz = "Uster mustern";
Häufig ist es einfacher, den Satz der Defaultvariablen $_
zuzuordnen,
welche natürlich skalar ist. Damit können wir die Verwendung der Mustererkennungs-Operatoren umgehen und einfach schreiben:
if (/uster/) {
print "Wir sprechen von Mustern\n";
}
Die $_
-Variable, ist die Default-Variable für viele Perl-Operationen und -Funktionen und wird sehr häufig gebraucht.
Reguläre Ausdrücke können aber sehr viel mehr als reiner Vergleich von Zeichenketten. Es gibt viele Spezialzeichen, die eine bestimmte Bedeutung haben. Mit diesen Spezialzeichen wird erst die volle Funktionalität (und auch die Komplexität) der RA's erreicht. Es ist empfehlenswert, mit einfachen RegEx zu beginnen und sich langsam zu steigern. Die hohe Schule der RegEx braucht Erfahrung und Kreativität.
Als Einstieg schauen wir uns ein paar einfache Beispiele an. Nehmen wir an, wir hätten
einen String und möchten wissen, ob darin die Zeichenfolge ac vorkommt.
Der RA dazu ist /ac/
. Anstelle von ac möchten wir auch
noch bc zulassen, dann heisst der RA /[ab]c/
. Falls nun
mehrere a's und b's vor dem c erlaubt
sein sollen, können wir schreiben: /[ab]+c/
. Folgende Menge von Strings
sind damit erlaubt:
ac
bc
aac
bc
abc
bac
aaac
und so weiter. Schreiben wir anstelle des +
-Zeichens ein *
wäre auch ein c alleine erlaubt.
Wenn wir am Anfang sicher ein a
wollen, müssen wir /a[ab]*c/
schreiben.
Welche Strings aus der obigen Liste werden damit erkannt?
Falls die obige Zeichenfolge am Anfang des Strings vorkommen soll, schreibt es sich so: /^a[ab]*c/
,
oder am Ende: /a[ab]*c$/
.
Anstelle unserer Zeichenfolge möchten wir Zahlen haben. Für Zahlen gibt es ein
Spezialzeichen \d
, was nichts anderes bedeutet als [0123456789]
oder in Kurzform [0-9]
. Diese drei Darstellungen sind äquivalent.
Nehmen wir an, wir hätten eine Nummer am Anfang unseres Strings und anschliessend ein Leerzeichen.
Der RA heisst /^\d+ /
.
Es könnte aber auch sein, dass anstelle eines Leerzeichens auch ein Tabulator oder ein Newline steht, dazu gibt es
wiederum ein Spezialzeichen, das \s
. Also heisst der RA /^\d+\s/
.
Verlassen wir unser Beispiel und schauen uns weitere Spezialzeichen an:
. # Ein einzelner Buchstaben ohne newline
^ # Zeilen- oder Stringanfang
$ # Zeilen- oder Stringende
* # Null oder mehrere Male den letzten Buchstaben, greedy!
*? # ditto, aber minimal
+ # Ein oder mehrere Male den letzten Buchstaben
? # Null oder ein Mal den letzten Buchstaben
und dazu auch gleich ein paar weitere Beispiele. Wir erinnern uns daran, das ein RA
zwischen /
.../
stehen soll.
t.e # t gefolgt von einem bel. Buchstaben
# gefolgt von e
# Dieses Muster ist enthalten in
# the
# tre
# tle
# aber nicht in te
# oder tale
^f # f am Anfang einer Zeile
^ftp # ftp am Anfang einer Zeile
e$ # e am Ende einer Zeile
tle$ # tle am Ende einer Zeile
und* # un gefolgt von 0 oder mehreren d
# Dieses Muster ist enthalten in
# un
# und
# undd
# unddd (etc)
.* # Irgendein String ohne newline, weil
# . bedeutet irgendein Buchstabe ausser newline
# und * bedeutet 0 oder mehrere davon
^$ # Leerzeile
Aber das ist noch längst nicht alles. Eckige Klammern werden verwendet um irgendein
Zeichen innerhalb zu erkennen. Wird innerhalb von eckigen Klammern ein Bindestrich -
verwendet, bedeutet das einen Zeichenbereich, ein ^
am Anfang bedeutet
'keines von diesen':
[qjk] # Entweder q oder j oder k
[^qjk] # Weder q noch j noch k
[a-z] # Irgendetwas zwischen a und z (inklusive)
[^a-z] # Keine Kleinbuchstaben
[a-zA-Z] # Irgendein Buchstabe
[a-z]+ # Irgendeine Folge von Kleinbuchstaben
Hier können wir vielleicht vorläufig aufhören und zur Uebungsaufgabe übergehen. Der Rest ist hauptsächlich als Referenz gedacht.
Ein senkrechter Strich |
bedeutet ein "OR" und Klammern (
...)
werden verwendet, um Dinge zu gruppieren:
jelly|cream # Entweder jelly oder cream
(eg|le)gs # Entweder eggs oder legs
(da)+ # Entweder da oder dada oder dadada oder...
Und noch ein paar Spezialzeichen mehr:
\n # Zeilenumbruch
\t # Tabulator
\w # Irgendein alphanumerischer (word) Buchstaben
# ist identisch mit [a-zA-Z0-9_]
\W # nicht alphanumerisch (non-word)
# ist identisch mit [^a-zA-Z0-9_]
\d # Eine Zahl. Ist identisch mit [0-9]
\D # Keine Zahl. Ist identisch mit [^0-9]
\s # 'whitespace character': space,
# tab, newline, etc
\S # 'non-whitespace character'
\b # Wortgrenze (nur ausserhalb [])
\B # Innerhalb eines Wortes
Zeichen, wie $
, |
, [
, )
,
\
, /
sind Spezialfälle in RA's. Falls sie als normale
Zeichen verwendet werden sollen, müssen sie mit einem 'backslash' \
markiert werden:
\| # Vertical bar
\[ # An open square bracket
\) # A closing parenthesis
\* # An asterisk
\^ # A caret symbol
\/ # A slash
\\ # A backslash
und so weiter.
Wie wir schon erwähnt haben, ist es vermutlich das Beste, mit einfachen Beispielen zu
beginnen und langsam zu schwierigeren zu gehen. Im Perl-Programm müssen sie wie gesagt
zwischen Schrägstrichen /
.../
stehen.
[01] # Enweder "0" or "1"
\/0 # Eine Division durch Null: "/0"
\/ 0 # Mit Leerzeichen: "/ 0"
\/\s0 # Mit 'whitespace':
# "/ 0" wobei das Leerzeichen auch ein Tab
# etc. sein kann0
\/ *0 # Kein oder mehrere Leerzeichen
# "/0" or "/ 0" or "/ 0" etc.
\/\s*0 # Kein oder mehrere 'whitespace'
\/\s*0\.0* # Wie vorher, aber mit Dezimalpunkt
# und möglicherweise weiteren Nullen, zB.
# "/0." und "/0.0" und "/0.00" etc und
# "/ 0." und "/ 0.0" und "/ 0.00" etc.
Im letzten Kapitel zählte unser Programm alle nicht-leeren Zeilen im File roman5.txt. Wir wollen es nun ändern, sodass es anstelle von den nicht-leeren Zeilen, nur Zeilen mit folgenden Eigenschaften zählt:
\b
um
Wortgrenzen zu erkennen. Das Programm soll weiterhin jede Zeile ausgeben, jedoch nur die obenerwähnten
nummerieren. Wir wollen die $_
-Variable verwenden um den
Mustererkennungs-Operator =~
zu vermeiden.