Homepage Inspire-world | Forum
| CGI/Perl Workshops Mustervergleiche mit regulären Ausdrücken |
| |
Inhaltsverzeichnis/Workshops Übersichtsseite/Mustervergleiche mit regulären Ausdrücken
Hinweis: Das Kapitel ist noch nicht komplett fertiggestellt
Vorwort
Operatoren und Ausdrücke für das Pattern Matching
Der Reihe nach... Einfache Muster
Metazeichen
Übereinstimmungen am Musteranfang oder Musterende
Vergleichen von Alternativen
Zeichengruppen und besondere Zeichenklassen
Mehrere Übereinstimmungen finden und Quantifizierer einsetzen
Die Anzahl der Wiederholungen eingrenzen
Vorwort:
Hier einiges zum Thema "Pattern-Matching" (zu Dt. Mustervergleiche) mit regulären Ausdrücken. Nun ist das mit den
"regulären Ausdrücken" für einen Perl Einsteiger eher ein Buch mit 7 Siegeln. Grund dafür ist die zunächst etwas verwirrende Syntax. Ok, es gibt auch ganz einfache aber es gibt auch reguläre Ausdrücke das
einem die Augen rollen.
Wofür wird den das gebraucht?
Nun Mustervergleiche werden bei Perl in vielfältigster Weise eingesetzt. Ein großes Feld ist z.B. die Daten eines Formulares auf Richtigkeit zu prüfen, unzulässige Zeichen abzufangen, auf Übereinstimmungen zu checken oder
Zeichen oder Zeichengruppen zu ersetzen durch andere uvam.
m, s, Ja oder Nein?
Bei Mustervergleichen gibt es auch verschiedene Möglichkeiten der Anwendung.
m steht dabei für Muster, s für Substitution (ersetzen) das ! ist eine Verneinung.
Einfache Beispiele für Muster:
Prüfung auf Entsprechung
Also ist der Inhalt des Strings $variable identisch mit "wort" dann ... Reaktion auf Ergebnis
if ($variable =~ m/wort/) {
... Reaktion auf Ergebnis
}
Prüfung auf nicht Entsprechung
Also ist der Inhalt des Strings $variable nicht identisch mit "wort" dann ... Reaktion auf Ergebnis
if ($variable =!~ m/wort/) {
... Reaktion auf Ergebnis
}
Einfache Beispiele für Musterersetzungen:
Aufbau: s/muster/ersetzung/Option
Enthält der String $variable einen . dann entferne diesen
$variable =~ s/\.//g;
Top
Operatoren und Ausdrücke für das Pattern Matching
Zum erstellen eines regulären Ausdrucks werden zwei Operatoren benötigt.
1. Der Operator für reguläre Ausdrücke m//
2. Einen Pattern-Matching Operator =~
Ein einfaches Beispiel dazu:
if ($variable =~m/Suchmuster/) {
... Reaktion auf Ergebnis
}
Allerdings gib es auch hier Kuzschreibweisen die man anwenden kann.
if ($variable !~ /^[0-9]*$/) {
... Reaktion auf Ergebnis
}
Im obigen Beispiel könnte man eine Fehlermeldung ausgeben wenn andere/nicht (dafür steht das ! Ausrufezeichen)
Zeichen als 0-9 in der $variablen vorkommen. Das Ausrufezeichen ist die Verneinung in dem Fall.
Nun gleich noch etwas zu dem Muster: /^[0-9]*$/
Das Caret ^ Zeichen dient dazu festzulegen dass das Suchmuster mit einer Zahl, in dem Fall hier die Zeichenklasse [0-9]
beginnen muss. Das Zeichen * ist ein Multiplikator und das Zeichen $ steht in einem Suchmuster für das Stringende.
In Worte gefasst würde das so lauten:
Prüfe ob der String von Anfang bis Ende nur aus Zahlen beliebiger Anzahl besteht.
Top
Der Reihe nach... Einfache Muster
Als einfache Muster kann man all jene Zeichenfolgen bezeichnen die nicht mit speziellen Metazeichen (siehe nächsten Abschnitt)
versehen sind.
Beispiele:
/123/
/2 Worte/
/zwei Worte/
/oder auch mehrere Worte/
Hier wird nur eine Übereinstimmung gefunden wenn die Muster genau passen.
Top
Metazeichen
Metazeichen sind Zeichen die einen regulären Ausdruck mit weitergehender Funktionalität ausstatten. Will man in einem regulären Ausdruck nach solchen
Metazeichen suchen müssen diese mit einem vorangestellten Backslash escapt (Beispielsweise so: \*) werden.
Liste aller Metazeichen:
^ . ? { ( ) / [
$ + * \ |
Top
Übereinstimmungen am Musteranfang oder Musterende
Oftmals kommt es vor das man Muster ab dem Anfang oder auf ein Musterende hin prüfen muss.
Hierfür werden die Zeichen:
^ für Musteranfang
$ für Musterende
benutzt.
Beispiele:
/^Anfang/ Das Muster muss mit "Anfang" beginnen
/Ende$/ Das Muster muss mit "Ende" aufhören
Will man nach Mustern suchen die an einer Wortgrenze vorkommen, dazu zählen Buchstabe, Zahl und der Unterstrich, benutzt man
das \b - für das Gegenteil, also die Nichtwortgrenze das \B.
Beispiele:
/\bei\b/ findet nur das Wort "ei" wenn es allein steht, nicht aber Beispiel
/\bSpiel/ findet in Strings wie: "Der Spielplatz" oder auch "Spiele auf dem Fußballplatz" das Muster
Benutzt man dagegen \B führt /\Bei/ nur zu Treffern wenn "ei" innerhalb eines Wortes vorkommen wie in Beispiel.
Top
Vergleichen von Alternativen
Hier geht es darum eines oder mehrere Muster zu finden. Um diese miteinander zu verknüpfen verwendet man das Boolesche "UND" (&& oder auch and)
oder das "ODER" (|| oder or).
Beispiel:
if ($string =~ /Herr/) || ($string =~ /Frau/)) {...
Wenn nun der zu durchsuchende String entweder "Herr" oder "Frau" enthält ist der Test wahr.
Allerdings gibt es hierfür eine wesentlich kürzere Schreibweise, dazu wird das Metazeichen der Pipe | verwendet. Eine solche Schreibweise wie im folgenden Beispiel wird auch als Alternation bezeichnet.
if ($string =~ /Herr|Frau/) { ...
Auch hier ist der Test wahr wenn entweder "Herr" oder "Frau" in dem String vorkommt.
Bei der Schreibweise des Musters:
/^Herr|Frau/
Wird "Herr" nur am Anfang gefunden, "Frau" kann dagegen überall vorkommen. Will man auch dies so notieren das beide nur am Anfang gefunden werden schreibt man so:
/^Herr|^Frau/ besser ist es aber dies zusammenzufassen zu einer Gruppe /^(Herr|Frau)/
Bei dieser Schreibweise prüft Das Script zuerst den Zeilenanfang und dann die weiteren Zeichen von Herr. Wird nichts gefunden wird am Zeilenanfang erneut begonnen und
auf Frau geprüft. Zuerst wird also immer auf das linke Muster vor der Alternation geprüft.
Wenn es sich anbietet, z.B. bei "Weise" und "Weiß" kann man das Muster so schreiben:
/^Wei(se|ß)/
Da in dem Muster beide Begriffe mit "Wei" beginnen kann man hier einige Durchläufe sparen. Dies macht die ganze Sache um einiges effizienter.
Man kann in solchen Mustern natürlich auch mehr als nur Zwei Alternativen zusammenfassen.
/(1.|2.|3.|4.|5.|6.) Kapitel/
Dies würde dann Treffer ergeben für 1. Kapitel, 2. Kapitel usw.
Top
Zeichengruppen und besondere Zeichenklassen
1. Zeichenklassen
Möchte man in einem String eines der folgenden 4 Worte finden könnte man dies so schreiben:
/Hut|Mut|Tut|Wut/
Allerdings ist das nicht grade sehr effizient, besser ist da schon
/(H|M|T|W)ut/
Hier wird zuerst nach den 4 Buchstaben HMTW gesucht und dann wird auf "ut" geprüft. Diese Art von Musterschreibweise kann man nun noch weiter
verbessern indem man das zu einer Zeichenklasse zusammenfast. Zeichenklassen werden in eckigen Klammern [] notiert. Für das letzte Beispiel sähe das dann so aus
/[HMTW]al/
Die Zeichen die in den eckigen Klammern stehen werden dabei zu ganz normalen Zeichen, auch die meisten Metazeichen.
Die meisten deshalb weil man eine eckige Klammer natürlich mit einem Backslash escapen muss, dem Bindestrich der eine besondere Bedeutung hat und dem Caret Zeichen ^
welches nicht an erster Stelle vorkommen darf.
2. Bereiche
Ein Bereich ist sozusagen die Kurzschreibweise einer Zeichenklasse. Sucht man nach Zahlen von 0-9 kann man
/0123456789/ schreiben oder einen Bereich definieren der dann so notiert wird /[0-9]/
Man kann auch mehrere Bereiche zusammen notieren /[a-z0-9]/ wäre hierfür ein Beispiel.
3. Zeichenklassen negieren
Um in einem Muster Zeichen auszuschließen verwendet man das Caret Zeichen innerhalb der eckigen KLammern.
/[^0-9]/ Hier würde dann alles außer Zahlen verglichen. Will man aber einmal nach einem Caretzeichen suchen so muss dies mit einem Backslash
escapt werden. /[\^.?]/ würde nach dem Caretzeichen, dem Punkt und dem Fragezeichen suchen.
4. Spezielle KLassen die verwendet werden können.
Oftmals ist es so das man selbst mit den Bereichsbeschreibungen noch viel zu viel zu tippen hat. Dafür gibt es einige besondere Klassen die
einiges an Zeichen zusammenfassen.
Code Gleichbedeutende Zeichenklasse Entspricht
\d [0-9] Alle Ziffern
\D [^0-9] Alle Zeichen AUSSER Ziffern (negiert)
\w [0-9A-Za-z_] Alle Worzeichen und der Unterstrich _
\W [^0-9A-Za-z_] Alle Zeichen AUSSER Worzeichen und der Unterstrich _ (negiert)
\s [\n\r\t\f] Whitespace Zeichen Leerzeichen, Wagenrücklauf, neue Zeile, Tabulator und Blattvorschub
\S [^\n\r\t\f] Alle Zeichen AUSSER Whitespace Zeichen
Zu \w und \W noch etwas zur Erklärung:
Der Begriff Wortzeichen hat im Grunde wenig mit "Worten" zu tun, mit diesen Zeichen werden alle zulässigen Zeichen
die auch in Variablennamen verwendet werden dürfen bezeichnet. Ein Interpunktionszeichen fallen nicht in den Bereich und werden deshalb nicht den Wortzeichen zugeordnet.
Mit den oben aufgelisteten Zeichenklassen kann man nun ein Muster für z.B. Postleitzahlen so schreiben
/\d\d\d\d\d/
Top
Mehrere Übereinstimmungen finden und Quantifizierer einsetzen
Oft kommt es vor das man nach mehreren Vorkommen suchen will oder festellen möchte wieviele male etwas vorkommt. Dazu werden die Quantifizierer eingesetzt.
Quantifizierer sind Metazeichen in Regulären Ausdrücken mit denen man angeben kann wie oft eine Zeichenfolge oder auch nur ein Zeichen vorkommen soll.
Perl kennt 3 Metazeichen die als Quantifizierer eingesetzt werden: ?, * und +
Optionale Zeichen finden mit dem Quantifizierer ?
Das ? wird benutzt um das Zeichen vor dem ? als optional gelten zu lassen.
Beispiel: /Walth?er/ Dies würde sowohl "Walter" als auch "Walther" finden.
Besteht der optionale Teil aus einer Zeichengruppe kann man dies in Klammern notieren.
Beispiel: /Bohr(maschine)?/ Hier wird "maschine" optional.
Der Quantifizierer *
Im Gegesatz zum Quantifizierer ? bewirkt dieser Quantifizierer * dass das Zeichen keinmal oder in beliebiger Anzahl vorkommen kann.
Beispiel: /Wal.*/ Das würde Treffer ergeben für all das was mit "Wal" anfängt, das könnte dann eben nur "Wal" sein aber auch Wald, Walter, Waldarbeiter usw...
Der Quantifizierer +
Bei diesem Quatifizierer muss mindestens ein Zeichen oder Zeichenfolge gefunden werden oder mehrere.
Beispiel: $text =~ s/^\s+//; Hier in dem Beispiel wird in $text nach Leerzeichen am Stringanfang (^) von mindestens 1 bis beliebig vielen gesucht, ist mindestens 1 oder mehrere vorhanden werden diese alle entfernt.
Das ist wichtig wenn z.B. ein Benutzer einen Dateinamen per Formular übermittelt und dieser Dateiname dann vom Script als Datei angelegt werden sollte. Ein oder mehrere Leerzeichen am Dateianfang würden hier ziemlichen Ärger nach sich ziehen.
Hinweis
Bei allen drei hier vorgestellen Quantifizierern können Sie mit einzelnen Zeichen, Zeichenfolgen oder auch Zeichenklassen arbeiten.
Top
Die Anzahl der Wiederholungen eingrenzen
Verwendet man die Metazeichen + oder * kann die Zeichenfolge beliebig oft enthalten sein. Beim ? nur einmal.
Will man nun die Muster auf eine bestimmte Anzahl an Wiederholungen prüfen muss man diese Anzahl in geschweifte Klammern setzen.
Ein kleines Beispiel:
$text =~ m/\d{1,2}/;
Hier würde etwas gefunden wenn der String eine oder maximal 2 Ziffern enthält.
So kann man z.B. einen per Formular übermittelten Datum prüfen,
dies würde dann so aussehen:
$text =~ m/(\d{1,2}\.\d{1,2}\.\d{4})/)/;
Der Datum müsste dazu im Format x.x.xxxx oder xx.xx.xxxx vorliegen
Mit dem kleinen Script kann man dies testen:
#!/usr/bin/perl
# Fehlerausgabe an Browser
use CGI::Carp qw(fatalsToBrowser);
$text = "Der Datum 12.06.2003 kommt im Text vor!";
print "Content-type: text/html\n\n";
if ($text =~ m/(\d{1,2}\.\d{1,2}\.\d{4})/) {
print "Es wurde der Datum $1 gefunden";
} else {
print "Der Datum entspricht nicht den Vorgaben";
}
Nun ist aber noch das Problem das man ja auch als Datum den 55.99.9999 übermitteln könnte.
Die würde dann auch als korrekt gewertet. Nicht so praktisch :-(
Aber auch dafür gibts ein Mittel in Form eines Regulären Ausdrucks. Das folgende Beispielscript passt zwar nicht 100%tig zum Kapitel, es soll hier an der Stelle nur
einmal gezeigt werden was man mit Regulären Ausdrücken so alles "anstellen kann".
#!/usr/bin/perl
# Fehlerausgabe an Browser
use CGI::Carp qw(fatalsToBrowser);
$text = "Der Datum 12.02.1993 kommt im Text vor!";
print "Content-type: text/html\n\n";
# Das m/(\b((((0?[1-9]|[12] .. bis .. \d{2}))\b)/) { sollte in einer Zeile stehen!
if ($text =~ m/(\b((((0?[1-9]|[12][0-9])\.(0?[1-9]|1[0-2])\.)|(30\.((0?[13-9])|(1[0-2]))\.)|
(31\.(0?[13578]|1[02])\.))(\d{2}|(18|19|20|21)\d{2}))\b)/) {
print "Es wurde der Datum $1 gefunden";
} else {
print "Der Datum entspricht nicht den Vorgaben";
}
Erläuterungen dazu:
(0?[1-9]|[12][0-9])\.(0?[1-9]|1[0-2])\.)
Hier wird davon ausgegangen das alle Monate wenigstens 29 Tage haben, der Februar allerdings nur aller 4 Jahre. Will man noch prüfen ob Schaltjahr oder nicht
müsste hier eine zusätzliche Prüfung rein.
So könnte man den Regex lesen:
Tage können sein von [1-9] oder aus 1 + [0-9] oder 2 + [0-9] bestehen wobei die führende 0 optional 0? ist. Nun folgen die Monate
(0?[1-9]|1[0-2]), auch hier wieder 1-9 oder 10,11,12 möglich [0-2]
Nun folgt der zweite Abschnitt der mit dem oder Zeichen | beginnt.
Da alle Monate, ausser Februar 30 Tage haben steht dann (30\.((0?[13-9])|(1[0-2]))\.)
Würde man "lesen" als: 30 Tage haben alle Monate ausser der 2 ( sonst stünde hier [1-9] und der 10-12 Monat.
Nun kommen die Monate mit den 31 Tagen dran, wieder mit | vom 30 Tage Test getrennt.
Dies (31\.(0?[13578]|1[02])\.)) wüde man dann lesen als:
31 Tage haben die Monate 1,3,5,7,8, bezeichnet durch [13578] und die Monate 10 und 12 bezeichnet durch 1[02]
Zum Schluss dann die Jahreszahlenprüfung (\d{2}|(18|19|20|21)\d{2})
Hier muss diese mit zwei Ziffern \d{2} beginnen und darf nur vom 18. bis 21. Jahrhundert gehen (18|19|20|21) dazu dann nocheinmal zwei Zahlen
\d{2} die die Jahreszahl komplettieren.
Top
Zum Inhaltsverzeichnis/Workshops Übersichtsseite/Mustervergleiche mit regulären Ausdrücken