Mustererkennung
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 (RA) zur Mustererkennung, welche auch von vielen UNIX
Hilfsprogrammen verwendet werden.
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. Das ist die Schreibweise,
die man aus historischen Gründen auch fast immer sieht.
Der RA unterscheidet Gross- 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";
}
welche 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:
$_= 'Ein Muster ohne Wert';
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.
RA 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. Wir empfehlen,
mit einfachen RA's zu beginnen und sich langsam zu steigern. Die hohe Schule der RA's
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
bbc
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:
- dem Buchstaben q
- dem String Du
- dem String Du, welcher ein grosses oder ein kleines D haben kann
- das Wort Du mit oder ohne Grossbuchstaben. Verwende \b um
Wortgrenzen zu erkennen.
Das Programm soll weiterhin jede Zeile ausgeben, jedoch nur die oben erwähnten
nummerieren. Wir wollen die $_-Variable verwenden um
den Mustererkennungs-Operator =~ zu vermeiden.
|