Perl-Programmierung: Reguläre Ausdrücke
Einleitung
[Bearbeiten]Als erstes stellt sich die Frage, was Reguläre Ausdrücke (kurz: regex) sind und was man mit ihnen machen kann. Zum anderen warum sie gerade in einem Perl-Buch erklärt werden.
Reguläre Ausdrücke sind eine Art Mini-Programme. Sie wurden entwickelt, um das Arbeiten mit Texten komfortabler und leichter zu gestalten. Reguläre Ausdrücke sind vergleichbar mit „Mustern“ oder „Schablonen“, die auf einen oder mehrere unterschiedliche Strings passen können. Diese Muster können entweder auf einen String passen oder nicht passen.
Hierbei existieren zwei Kategorien von Regulären Ausdrücken. Einmal eine normale Mustersuche, zum anderen eine "Suche und Ersetze"-Funktion. Die normale Mustersuche wird zum einen darin verwendet, um ganze Eingaben oder Strings zu überprüfen oder einzelne Informationen aus einen String auszulesen. Die Suchen- und-Ersetzen-Funktion hat dabei eine ähnliche Funktion, wie Sie es von grafischen Texteditoren gewohnt sind, nur sind diese in Perl deutlich mächtiger.
Die englischen Begriffe für die Muster- und "Suche & Ersetze"-Funktion sind dabei: "Pattern matching" und "Substitution". Diese Begriffe sollte man kennen, da sie oft benutzt werden. Diese werden hier ebenfalls im weiteren Verlauf Verwendung finden.
Zum anderen stellt sich immer noch die Frage, was das Ganze mit Perl zu tun hat. Reguläre Ausdrücke werden deshalb behandelt, da sie ein zentraler Bestandteil von Perl sind. Sie werden in Ihrer Perl-Karriere wohl kein Perl-Programm finden, in dem nicht mindestens ein Regulärer Ausdruck vorkommt. Reguläre Ausdrücke sind dabei so fest in Perl verankert und wurden im Laufe der Zeit so stark ausgebaut, dass dieses viele andere Programmiersprachen kopierten. Sie werden Reguläre Ausdrücke in Sprachen wie: PHP, Java, C#, Python, JavaScript, Tcl, Ruby und noch vielen weiteren Programmiersprachen finden. Dabei wird diese Erweiterung meist als "Perl-kompatibel" beschrieben. Jedoch sind Reguläre Ausdrücke in keiner dieser Sprachen so fest implementiert und so leicht anzuwenden wie in Perl.
Man kann schon fast sagen, dass Reguläre Ausdrücke erst durch Perl ihre heutige Funktionalität bekommen haben.
Dabei reißt der Artikel die Möglichkeiten von diesen Ausdrücken nur an.
Der Aufbau der beiden genannten Typen sieht dabei folgendermaßen aus:
Pattern Matching:
m/Regex/Optionen
Substitution:
s/Regex/String/Optionen
Begrenzer
[Bearbeiten]Wie man am Pattern Matching sowie der Substitution erkennen kann, trennen die Slashes "/" die einzelnen Teile eines Regulären Ausdruckes. Allerdings haben Sie bei Perl die Wahl, jedes beliebige Sonderzeichen als Begrenzer zu wählen. Die Vorteile davon werden Sie zu schätzen wissen, wenn Sie praktische Erfahrung mit Regulären Ausdrücken sammeln. Um es kurz vorweg zu sagen: Sie haben damit die Wahl ein Zeichen zu wählen das nicht in Ihrer Regex vorkommt, und Sie können sich somit das Maskieren der Zeichen sparen.
Perl weiß anhand des ersten Zeichen, das nach dem "m" oder dem "s" folgt, welcher Begrenzer gewählt wurde. Es sind also auch folgende Schreibweisen erlaubt:
m!regex! s#Regex#String#Optionen s$Regex$String$Optionen s"Regex"String"Optionen ...
Eine spezielle Regel gilt für Zeichen, die ein öffnendes sowie schließendes Zeichen besitzen. Denn dort müssen die einzelnen Teile eingeklammert werden. Dies schaut folgendermaßen aus:
m(Regex) s(Regex)(String)Optionen s{Regex}{String}Optionen s<Regex><String>Optionen s[Regex][String]Optionen
Wie man sieht werden hier unterschiedliche Anfangs- sowie Endzeichen verwendet. Eine weitere Eigenschaft ist, dass nur das wirkliche Endzeichen die Regex respektive den String einklammert. Im folgenden praktischen Beispiel ist also auch folgendes möglich:
s((H)allo)(Welt)i
Wenn Sie etwas Erfahrung mit Regulären Ausdrücken haben, werden Sie sehen, dass diese Regex keinen Sinn ergibt. Allerdings dient das lediglich zur Veranschaulichung, dass hier wirklich "(H)allo" als Regex erkannt wird, und nicht "(H" wie man annehmen könnte.
Wenn Sie noch nicht verstehen, was hier passiert, dann ist dies nicht weiter schlimm, wir werden später darauf zurückkommen.
Bindungsoperator
[Bearbeiten]Wie die Form eines Regulären Ausdrucks ausschaut, wissen Sie. Jedoch wissen Sie noch nicht, wie man diesen auf einen String anwendet. Hierfür existiert der sogenannte Bindungsoperator. Um einen String mit einer Regex zu verbinden, egal ob nun "Pattern Matching" oder "Substitution", schreiben Sie einfach folgendes:
$text =~ m/Regex/;
$text =~ s/Regex/String/;
Dieser Bindungsoperator prüft im ersten Fall, ob in $text das angegebene Muster auf der rechten Seite vorkommt. Zur Substitution kommen wir noch später; allerdings sei hierzu gesagt, dass jedes Vorkommen der angegebenen Regex in $text durch String ersetzt wird.
Weiterhin besitzt dieser Ausdruck einen Rückgabewert. Wenn das Muster, das in Regex angegeben wird, wirklich in $text passt, dann wird "true" zurückgegeben. Das gleiche gilt für die Substitution. Wenn die Regex auf $text gepasst hat, wurde eine Ersetzung durchgeführt und es wird der Wert "true" zurückgegeben.
Sollte die Regex in beiden Fällen nicht auf den String in $text gepasst haben, dann wird "false" als Wert zurückgeliefert. Bei der Substitution hat dies noch die Auswirkung, dass eine Ersetzung nicht stattgefunden hat.
Grundlegendes zu Regulären Ausdrücken
[Bearbeiten]In diesem Kapitel, erfahren Sie grundlegende Einzelheiten wie Reguläre Ausdrücke funktionieren. Sie werden einzelne Stolpersteine kennenlernen, und lernen sie zu vermeiden. Nach diesem Kapitel sollten Sie in der Lage sein Reguläre Ausdrücke auf beliebige Strings anzuwenden, und nur das zu finden, zu ersetzen oder die Informationen zu extrahieren die Sie wollen.
Pattern Matching
[Bearbeiten]Mit dem jetzigen Wissen wollen wir ein Pattern Matching ausführen. Wir wollen nach einem bestimmten Wort in einem String suchen, und sofern gefunden, wollen wir dies dem Benutzer mitteilen.
Ein konkretes Beispiel:
$string = "Wir mögen Kamele, auch wenn sie übel riechen";
if ($string =~ m/Kamel/)
{ print "Kamel gefunden!"; }
else
{ print "Schade, kein Kamel weit und breit."; }
Die Ausgabe des Perl-Scripts wäre "Kamel gefunden". Gehen wir die einzelnen Zeilen zusammen durch. In Zeile 1 wird ein Skalar mit dem Text "Wir mögen Kamele, auch wenn sie übel riechen" definiert. Zeile 2 ist nun neu für uns. Hier wird der Rückgabewert von unserem Ausdruck "$string =~ m/Kamel/" in einer bedingten Anweisung (if) ausgewertet. Wie wir wissen, wird "true" zurückgegeben, wenn unser Suchmuster in $string vorkommt. Wenn unser Suchmuster nicht vorkommt, wird "false" als Wert zurückgegeben. Dies hat direkte Auswirkung auf unsere if-Kontrollstruktur. Wenn unser Muster gefunden wird, dann wird "true" zurückgegeben und dadurch Zeile 3 ausgeführt. Wenn das Muster nicht in $string vorkommt, wird "false" zurückgegeben und Zeile 5 ausgeführt.
Aber warum finden wir denn überhaupt unser "Kamel"? Im String steht doch "Kamele". Dies ist zwar richtig, aber Perl interessiert sich nicht für Wörter. Perl liest unser Muster folgendermaßen: Ein "K", gefolgt von "a", gefolgt von "m", gefolgt von "e", gefolgt von "l". Unser komplettes Muster wird also an folgender unterstrichener Stelle gefunden.
Wir mögen Kamele, auch wenn sie übel riechen
An diesem Punkt wird unsere Suche auch sofort abgebrochen. Was danach kommt ist unwichtig. Unsere Regex hat auf das Muster gepasst und es wird als Rückgabewert "true" zurückgegeben.
Hier haben wir 2 Sachen gelernt:
- Die Mustersuche erkennt lediglich ein Zeichen nach dem anderen, ohne einen höheren Zusammenhang, also z.B. ein Wort daraus bilden zu können.
- Die Mustersuche wird sofort beendet sobald unsere Regex vollständig passt.
Zwei wichtige Punkte, auf die Sie später immer wieder stoßen werden.
Wissen Sie, was ausgegeben werden würde, wenn Sie den Ausdruck in der oberen if-Kontrollstruktur folgendermaßen anpassen würden?
$string =~ m/kamel/
Richtig! Es würde "Schade, kein Kamel weit und breit." ausgegeben werden! Warum? Perl interpretiert die Mustersuche folgendermaßen: Ein kleines "k", gefolgt von einem kleinen "a", gefolgt von einem kleinen "m" ...
Also nochmals zur Verdeutlichung. Es werden wirklich nur Folgen von Zeichen erkannt, ohne diese zu Interpretieren. Ein großes "K" ist ein anderes Zeichen als ein kleines "k" und wird folglich nicht gefunden. Da im ganzen $string kein "k" gefolgt von "a" ... vorkommt wird letztendlich "false" als Wert zurückgegeben, und unser "else"-Block wird ausgeführt.
Allerdings brauchen Sie keine Angst haben, dass Sie jetzt jede Schreibweise von "Kamel" ( "kAmEl", "KAMel", ... ) überprüfen müssen. Es gibt bestimmte Optionen die Sie aktivieren können, wodurch eine Groß- und Kleinschreibung nicht unterschieden wird.
Substitution
[Bearbeiten]Bevor wir uns nun mit den Optionen befassen, wollen wir eine Substitution durchführen. Hierfür passen wir das vorherige Beispiel etwas an:
$string = "Wir mögen Kamele, auch wenn Kamele übel riechen";
$string =~ s/Kamel/Lama/;
print $string;
In Zeile 1 wird ein String initialisiert, danach führen wir eine Substitution auf $string aus. Dort wollen wir "Kamel" durch "Lama" ersetzen. Unser Ergebnis wird danach zurückgegeben.
Wissen Sie bereits was zurückgegeben wird? Denken Sie erst darüber nach, bevor Sie sich die Lösung anschauen:
Wir mögen Lamae, auch wenn Kamele übel riechen
Überrascht? Gehen wir die Substitution einzeln durch. Unsere Substitution sagt, dass wir das Vorkommen von einem großen "K" gefolgt von einem kleinen "a" ... durch "Lama" ersetzen wollen. Wir müssen also jedes Vorkommen von "Kamel" erst einmal finden, und es danach durch "Lama" ersetzen. Damit wir unser "Kamel" überhaupt finden, beginnt unsere Substitution an der ersten Stelle unseres Strings. Hier wird überprüft ob das "K" auf das "W" von "Wir" passt. Das passt natürlich nicht, also springen wir ein Zeichen weiter. Es wird nun überprüft ob das "i" auf das "K" passt. Dies passt natürlich nicht, und unsere Substitution geht so lange den String weiter, bis es auf das erste "K" von "Kamel" passt. Jetzt ist gefordert, dass ein "a" folgt. Dies kommt wirklich vor, und die Substitution springt ein Zeichen weiter, solange bis unser ganzes "Kamel" erkannt wurde. Nun ist unsere Regex erfolgreich erkannt worden, und unser "Kamel" wird durch "Lama" ersetzt. Wichtig ist, dass unser folgendes "e" von "Kamele" nicht erkannt wurde, da wir nicht wissen wie wir Wörter erkennen können (jedenfalls noch nicht). Die Ersetzung wird also durchgeführt, und unsere Substitution war erfolgreich. Es wird "true" als Rückgabewert zurückgeliefert, und die Substitution beendet. Allerdings wird der Rückgabewert "true" verworfen. Unsere Substitution dringt erst gar nicht bis zum zweiten "Kamele" vor, und ersetzt dieses auch nicht, weil unsere Substitution bereits vorher erfolgreich war.
Hier lernen wir also wieder einige Neuigkeiten:
- Ein String wird von links nach rechts durchgearbeitet.
- Es wird immer der frühestmöglichste Treffer links gefunden.
Um jetzt jedes Vorkommen vom "Kamel" durch "Lama" zu ersetzen, könnten wir folgendes schreiben:
$string = "Wir mögen Kamele, auch wenn Kamele übel riechen";
while ($string =~ s/Kamel/Lama/) {}
print $string;
Wenn also unser "Kamel" gefunden und erfolgreich ersetzt wurde, wird als Rückgabewert "true" geliefert. Diese Rückgabe bringt while dazu, die leere Schleife noch einmal anzustoßen. Anschließend wird erneut unsere Bedingung durchgeführt. Zu beachten ist, dass unsere Substitution wieder ganz von vorne beginnt, also an der ersten Stelle im String anfängt, und nicht an der Stelle, an der wir zuletzt etwas verändert haben. Da das erste "Kamel" bereits beim ersten Durchlauf durch "Lama" ersetzt wurde, findet und ersetzt die Substitution trotzdem das zweite Vorkommen von "Kamel". Manchmal ist dieses Verhalten gewünscht, aber in den seltesten Fällen ist dies der Fall, und es kann hierbei zu Problemen kommen. Stellen Sie sich folgendes while Konstrukt vor:
while ( $string =~ s/e/ee/ ) {}
Hiermit hätten wir eine Endlossschleife gebaut. Das erste Vorkommen eines "e" wird durch ein "ee" ersetzt. Der Rückgabewert ist true und unsere Substitution beginnt wieder von vorne. Jetzt sind zwei "e" an der Stelle vorhanden, und dort ersetzen wir wieder das erste "e" durch "ee". Es existieren also schon 3 "e" an dieser Stelle. Dieses wird jetzt unendlich oft durchgeführt. Jedenfalls solange bis Perl abstürzt, weil Sie nicht genügend RAM haben um so einen großen String zu speichern.
Sie sollten also aufpassen bei solch einer Verwendung. Es gibt zwar eine Option, die unserem gewünschten Verhalten entspricht und bei der letzten Substitution weitermacht. Aber manchmal benötigen wir das obige Verhalten.
Optionen
[Bearbeiten]Optionen dienen dazu das Verhalten unserer Regex zu verändern. Mit ihnen sind Sachen möglich, die so ohne weiteres nicht möglich wären. Jede Option kann dabei unsere Regex grundlegend verändern. Die Optionen werden hierbei hinter den Regulären Ausdruck angehängt. Es ist auch möglich mehrere Optionen zu benutzen, die Reihenfolge der Optionen spielt hierbei keine Rolle. Den grundlegenden Aufbau sehen Sie in der Einleitung, hier aber nochmals ein paar praktische Beispiele:
m/kamel/i
m/k a m e l/xi
s/kamel/lama/ig
s/k a m e l/igx
Einige wichtige Optionen sollen hierbei bereits erläutert werden, andere wenn sie wichtig werden:
Option: /i
[Bearbeiten]Mit der Option "i" ignore-case können wir Perl dazu veranlassen, dass zwischen Groß- und Kleinschreibung nicht mehr unterschieden wird. Folgendes Muster:
m/kamel/i
Würde also auch auf "KAMEL", "KAmel", "KaMeL", ... passen.
Option: /g
[Bearbeiten]Im Kapitel "Substitution" wollten wir, dass jedes Vorkommen von "Kamel" durch "Lama" ersetzt wird, bei der folgenden Lösung mit der while-Schleife funktionierte das zwar, jedoch könnte dieses unter Umständen zu neuen Problemen führen. Das "g" steht hier für global und es möchte damit ausdrücken, dass wir den ganzen String durcharbeiten. Genau gesagt bewirkt "/g", dass die Substitution nach einer Ersetzung nicht beendet wird, sondern an der letzten Position weiter macht, und nach weiteren Treffern sucht. Der Rückgabewert ist hierbei die Anzahl von Substitutionen, die innerhalb des Strings durchgeführt wurden. Wenn also mindestens eine Substitution durchgeführt wurde, ist der Rückgabewert automatisch "> 0" und somit ein "wahrer" Wert.
Hierbei ist zu beachten, dass die Option nur Auswirkung auf eine Substitution hat. Wir können z.B. nicht mit:
$string =~ m/e/g;
Die Anzahl der "e" Buchstaben innerhalb eines Strings zählen.
Dadurch, dass unsere Substitution jedoch nicht immer wieder von vorne anfängt, können wir das letzte Problem im Kapitel "Substitution" lösen.
$string = "Wir mögen Kamele, auch wenn Kamele übel riechen";
$string =~ s/e/ee/g;
print $string;
Dies würde nun nicht mehr in eine Endlosschleife enden, sondern folgenden String zurückgeben:
Wir mögeen Kameelee, auch weenn Kameelee übeel rieecheen
Option: /x
[Bearbeiten]Normalerweise werden innerhalb einer Regex Whitespace-Zeichen als zu dem Muster gehörend angesehen.
m/a b/
Diese Regex würde auf ein "a" gefolgt von einem Leerzeichen gefolgt von einem "b" passen. Mit dieser Option verliert das Leerzeichen seine Bedeutung, und wir würden ein "a" gefolgt von einem "b" suchen.
Unter Whitespace-Zeichen versteht man alle Zeichen, die nicht direkt etwas auf dem Bildschirm zeichnen. Dies sind zum Beispiel Leerzeichen, Tabulatoren, Newlines und Formfeeds.
Wahrscheinlich werden Sie den Sinn dahinter noch nicht nachvollziehen können. Für die sehr kleinen Regexe, die wir bisher geschrieben haben, ist dieses auch unbrauchbar. Allerdings werden wir später auf sehr viel komplexere Regexe stoßen. Damit diese besser lesbar sind, wurde diese Option implementiert. Damit kann man eine Regex über mehrere Zeilen verteilen und ihnen sogar Kommentare geben.
Zusammenfassung
[Bearbeiten]- "i", case-insensitive: Groß- und Kleinschreibung wird ignoriert
- "g", global: Es werden alle Stellen gesucht, die passen. Die Funktion ist eher für das Ersetzen mit regulären Ausdrücken interessant.
- "m", multi-line: Verändert die Bedeutung des $-Zeichen (siehe Sonderzeichen), damit es an der Position vor einem "\n" passt.
- "s", single-line: Verändert die Bedeutung des .-Zeichen (siehe Sonderzeichen), damit es auch auf einem "\n" passt.
- "x", extended: Alle Whitespace-Zeichen innerhalb des Regulären Ausdrucks verlieren ihre Bedeutung. Dadurch kann man Reguläre Ausdrücke optisch besser aufbereiten und über mehrere Zeilen schreiben.
- "o", compile once: Ausdruck wird nur einmal kompiliert und kann nicht mehr verändert werden.
- "e", evaluate: Das Ersetzmuster wird als Perl-Code interpretiert und ausgeführt.
Quantoren
[Bearbeiten]Quantoren sind eine Möglichkeit auszudrücken, dass etwas bestimmt oft nacheinander folgt. Hierbei kann man die Anzahl selber bestimmen, oder man benutzt bereits vordefinierte Quantoren. Diese vordefinierten Quantoren wurden eingebaut, weil diese besonders oft vorgekommen sind. Sie werden durch Sonderzeichen definiert. Quantoren beziehen sich immer auf die Einheit die vor ihnen steht. Dies kann dabei ein einfacher Buchstabe, ein Zahl oder aber auch eine Gruppierung von Zeichen sein.
Stellen Sie sich vor, Sie möchten eine Folge von 15 "a" hintereinander erkennen. Sie könnten in Ihrer Regex das a nun 15mal schreiben, allerdings können Sie sich dabei leicht verzählen. Genauso wird es schwieriger Ihren Code zu Lesen. Daher wäre die Benutzung eines Quantors ideal.
m/a{15}b/i
Wie man erkennen kann, wird ein Quantor durch geschweifte Klammern definiert. Da sich der Quantor auf die vorherige Einheit bezieht, wird hier gesagt, dass das "a" 15 mal vorkommen muss. Der Quantor bezieht sich hier nicht auf das "b", wie man annehmen könnte. Was wäre aber, wenn wir nun eine Mindestgrenze und eine Maximalgrenze angeben wollen? Dies wäre auch kein Problem.
m/a{10,15}b/i
Hiermit sagen wir, dass das "a" mindestens 10 mal vorkommen muss und maximal 15 mal vorkommen darf. Danach muss ein "b" folgen. Nun wollen wir aber das ein "a" nur eine mindestgrenze erfüllen muss, es danach aber unendlich oft folgen darf. Um dies auszudrücken, lassen wir einfach die Maximalgrenze weg.
m/a{10,}b/i
Wichtig hierbei ist, dass das Komma stehen bleiben muss. Würden wir das Komma weg lassen, hätten wir eine genaue Angabe wie oft das "a" vorkommen muss. In diesem Beispiel muss das "a" nun mindestens 10 mal hintereinander vorkommen, darf aber auch unendlich oft vorkommen. Das Gleiche können wir auch für die Maximalangabe machen.
m/a{,10}b/i
Hiermit würde wir ein "a" maximal 10 mal finden, allerdings darf es auch kein einziges mal vorkommen.
Quantor: ?
[Bearbeiten]Dieser Quantor ist identisch mit {0,1}. Er drückt aus, dass das vorherige optional ist, d.h. 1-mal oder keinmal vorkommen darf.
m/\.html?/i
Dies würde z.B. auf die Dateiendung ".htm" sowie auch ".html" passen. Der Punkt muss hier durch ein "Backslash" maskiert werden, da der Punkt eine besondere Bedeutung hat. Genauso wie das Fragezeichen. Wollen Sie nicht die besondere Bedeutung des Fragezeichen haben, weil Sie nach einem Fragezeichen suchen wollen, müssen Sie dieses auch maskieren. Durch "\?" verliert das Fragezeichen seine Bedeutung als Quantor. Dies gilt ebenfalls für alle anderen Sonderzeichen.
Quantor: *
[Bearbeiten]Dieser Quantor ist identisch mit {0,}. Er drückt aus, dass das vorherige beliebig häufig vorkommen darf, also auch keinmal. Dieser Quantor wird erst interessant mit sogenannten Zeichenklassen.
m/\d*/i
Hier greife ich etwas vorweg. Das "\d" steht dabei für eine beliebige Ziffer von 0-9. Wenn wir den Stern-Quantor auf das "\d" anwenden, dann finden wir eine Folge von Ziffern. Allerdings muss keine Ziffer an dieser Stelle stehen, denn keinmal ist ja ebenfalls erlaubt. Bei der Verwendung des Sterns müssen Sie aufpassen. Nicht immer ist es das, was Sie wollen. Er würde auch auf "zweiundvierzig" passen, da es nunmal auch erlaubt ist, wenn keine Ziffer vorkommt.
Quantor: +
[Bearbeiten]Der Quantor ist identisch mit {1,}. Er drückt aus, dass das vorherige mindestens einmal vorkommen muss, aber unendlich oft folgen darf. Wie auch der Stern-Quantor ist dieser erst mit einer Zeichenklassen interessant.
m/\d+/i
Meistens ist es das, was Sie wollen. Es erkennt eine beliebige Anzahl von Ziffern. Dabei muss jedoch mindestens eine Ziffer vorkommen.
Gierigkeit
[Bearbeiten]Vielleicht haben Sie sich bei dem "?"-Quantor gefragt, was nun als erstes erkannt wird. Das "htm" oder das "html". Alle Quantoren die hier vorgestellt wurden sind gierig. Das bedeutet, dass, wenn sie die Wahl haben ein Zeichen zu erkennen oder nicht zu erkennen, sie es immer zuerst versuchen werden, es zu erkennen.
Bei dem Beispiel mit dem "?"-Quantor würde zuerst das ".htm" erkannt werden. Das "l" ist Optional, da es aber gierig ist, schaut es nach, ob das "l" wirklich danach kommt, und wird es ebenfalls aufnehmen wenn es folgt. Wenn es nicht folgen sollte, ist unsere Regex ebenfalls erfüllt, da es auch erlaubt war, wenn das "l" nicht auf ".htm" folgt.
Zeichenklasse
[Bearbeiten]Manchmal möchten Sie, dass an einer bestimmten Stelle eine Auswahl unterschiedlicher Zeichen stehen kann. Für dieses Problem gibt es die sogenannten Zeichenklassen. Eine Zeichenklasse wird mit eckigen Klammern umgeben. Hierbei ist zu beachten, dass die komplette Zeichenklasse für ein einziges Zeichen steht. Innerhalb der Zeichenklasse wird definiert, auf welches Zeichen die Zeichenklasse passen darf.
m/[234][12345]/
Dieses Matching würde auf eine zweistellige Zahl passen. Dabei muss die erste Ziffer 2, 3 oder 4 sein, und die zweite Ziffer 1, 2, 3, 4 oder 5. 50 oder 20 würden z.B. nicht gefunden werden.
Zeichenklassen-Metazeichen
[Bearbeiten]Innerhalb von Zeichenklassen ist der Bindestrich als sogenanntes Zeichenklassen-Metazeichen erlaubt. Hiermit können wir ganze Bereiche von Zeichen angeben. Wollen wir z.B. alle Zahlen erkennen, können wir das auf zwei Arten tun.
[0123456789] [0-9]
Diese beiden Zeichenklassen erkennen genau das gleiche. Bei Buchstaben wird der Vorteil offensichtlicher:
[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ] [a-zA-Z]
Möchten wir ein Bindestrich innerhalb einer Zeichenklasse erkennen, dann müssen wir diesen als erstes Zeichen schreiben.
[-a-zA-Z]
Dies würde auf alle Buchstaben inklusive des Bindestriches passen.
Negative Zeichenklasse
[Bearbeiten]Weiterhin gibt es eine negative Zeichenklasse. Dort können wir definieren, auf welche Zeichen unsere Zeichenklasse nicht passen darf. Möchten wir eine negative Zeichenklasse verwenden, dann muss das erste Zeichen innerhalb der Zeichenklasse ein Zirkumflex (^) sein. Wenn das Zirkumflex nicht an erster Stelle steht, wird es als normales Zeichen erkannt.
Wenn also jedes beliebige Zeichen außer einer Zahl erkannt werden soll, können wir folgendes schreiben:
[^0-9]
Zeichenklassen
[Bearbeiten]Dieser Abschnitt ist sehr verwandt mit der Zeichenklasse. Während wir bei der Zeichenklasse unsere Zeichen wählen konnten, können wir dies bei den Zeichenklassen nicht mehr. Diese Zeichenklassen dienen lediglich als Abkürzung für eine bestimmte Zeichenklasse:
- \d = digit Erkennt Ziffern. Ist das gleiche wie [0123456789]
- \D = Erkennt alles andere außer Ziffern. Ist das gleiche wie [^0123456789]
- \s = Erkennt jegliche Art von Whitespace-Zeichen: Leerzeichen, Tabulatoren, Newlines, Formfeed ...
- \S = Erkennt alles außer Whitespace-Zeichen.
- \w = word Erkennt Ziffern, Buchstaben und Unterstrich. Ist das gleiche wie [a-zA-Z0-9_]
- \W = Erkennt alles außer einem Wortzeichen.
- . = Punkt Steht für jedes Zeichen außer Newline. Ist das gleiche wie [^\n]
Alternation
[Bearbeiten]Unter Alternation verstehen wir dass entweder das eine, oder das andere passen kann. Es ist also eine "Oder"-Verknüpfung. Eine Alternation schreiben wir als "|" (Pipe) Zeichen. Hierbei wird dann entweder das genommen was links von diesem Zeichen steht, oder was rechts von diesem Zeichen steht.
Pattern Matching
[Bearbeiten]Bei der Alternation innerhalb des Pattern Matching gibt es wenig zu beachten. Stellen Sie sich vor, Sie schreiben ein Programm und möchten, dass sich das Programm nach diversen Eingaben des Benutzers beendet. Sie könnten folgendes Schreiben
$input = <STDIN>;
if ( $input =~ m/q|quit|exit/i ) { exit; }
print "Hallo, Welt!\n";
Dieses Programm wartet auf eine Eingabe des Benutzers. Würden wir "q", "quit" oder "exit" eingeben, dann würde sich unser Programm beenden. Bei allem anderen würden wir die Meldung "Hallo, Welt!" auf dem Bildschirm sehen.
Substitution
[Bearbeiten]Innerhalb einer Substitution gibt es einige Punkte die wir beachten müssen. Stellen Sie sich vor, Sie möchten jedes vorkommen von "q" oder "quit" durch "exit" ersetzen. Vielleicht würden Sie so etwas schreiben:
s/q|quit/exit/ig;
Dies funktioniert aber nicht richtig. Sollte wirklich "quit" im String vorkommen auf dem Sie diese Regex anwenden, dann würde folgendes dabei raus kommen:
exituit
Um die genaue Vorgehensweise zu verstehen, müssen Sie wissen wie Perl eine Alternation behandelt. Es wird wieder Zeichen für Zeichen überprüft. Dabei wird aber auch die Reihenfolge der Alternation beachtet. Es wird zuerst der Ausdruck ganz links überprüft und erst wenn dieser nicht passt, wird der nächste Ausdruck überprüft. Sollte ein Ausdruck passen, wird sofort die Substitution ausgeführt. Bei "quit" passt das "q" sofort und es würde hier eine Substitution mit "exit" statt finden. Dadurch würde ein "quit" nie im Text ersetzt werden, da wir schon vorher das "q" durch "exit" ersetzt haben. Wir müssen also die Reihenfolge vertauschen:
s/quit|q/exit/ig;
Merke:
- Bei der Alternation ist die Reihenfolge wichtig
Dieser Punkt gilt auch für das Pattern Matching, allerdings hängt es von der Verwendung ab, ob wir die Reihenfolge anpassen müssen. Im obrigen Beispiel ist es egal wodurch unser Programm beendet wird. Würden wir aber Informationen auslesen, kann es sehr wohl von Bedeutung werden, welche Reihenfolge wir in der Alternation gewählt haben.
Sonderzeichen
[Bearbeiten]Für die Suchmuster stehen diverse Sonderzeichen zur Verfügung. Diese können dann beispielsweise für beliebige Zeichen oder für das Zeilenende stehen.
- . steht für ein beliebiges Zeichen außer dem "\n".
- ? steht für das ein- oder nullmalige Vorkommen des vorangegangenen Zeichens oder Gruppierung. Folgt das Fragezeichen auf einen Quantor dann wird dieser zu einem nicht gierigen Quantor.
- + steht für das ein- oder mehrmalige Vorkommen des vorangegangenen Zeichen oder Gruppierung.
- * steht für das null- oder mehrmalige Vorkommen des vorangegangenen Zeichen oder Gruppierung.
- ^ steht für den Beginn einer Zeile.
- $ steht für das Ende eines Strings. Wenn die /m Option benutzt wird, wird die Bedeutung so verändert das es für ein "\n" Zeichen steht.
- \A steht für den Beginn eines Strings.
- \Z steht immer für das Ende eines Strings, unabhängig ob die Option /m verwendet wurde.
- \ - das nächste Zeichen wird ohne Funktion interpretiert. Um einen Punkt zu suchen muss "\." benutzt werden, sonst wird ein beliebiges Zeichen gefunden.
- [] wird Zeichenklasse genannt und steht für ein angegebenes Zeichen. [dhb]ot würde "dot", "hot" oder "bot" finden.
- () 1. Funktion: Gruppiert Ausdrücke. 2.Funktion: Speichert den tatsächlichen Wert, der an dieser Stelle gefunden wurde, in einer Variable.
- | steht für das logische ODER. "ist|war" gibt sowohl true, wenn "ist" im Ausdruck vorkommt, als auch wenn "war" im Ausdruck enthalten ist.
Zeichen ersetzen
[Bearbeiten](todo)
Zeichen ersetzen ohne Reguläre Ausdrücke
[Bearbeiten]Geht es nur darum, einzelne Zeichen durch andere zu ersetzen, sind Reguläre Ausdrücke häufig 'überqualifiziert', da es mit tr///
eine deutlich einfachere und performantere Alternative gibt.
$string =~ tr/SUCHEN/ERSETZEN/Optionen
SUCHEN und ERSETZEN sind dabei Auflistungen der zu ersetzenden Zeichen und ihren entsprechenden Ersetzungen. tr/abc/xyz/
ersetzt alle a mit x, alle b mit y und alle c mit z.
Zusätzlich zu einzelnen Zeichen können Bereiche angegeben werden. Ist die Liste ERSETZEN kürzer als die Liste SUCHEN, werden die übrigen Zeichen von SUCHEN mit dem letzten Zeichen von ERSETZEN ersetzt. Die beiden Ausdrücke tr/a-f/xyz/
und tr/abcdef/xyzzzz/
bewirken also das Gleiche.
Einige nützliche Beispiele:
$string =~ tr/A-Z/a-z/; # ersetzt alle Großbuchstaben durch Kleinbuchstaben
$string =~ tr/A-Z!-\/:-@[-`{-~/a-z /; # ersetzt zusätzlich ASCII-Sonderzeichen durch Leerzeichen
$string =~ tr/A-Za-z/N-ZA-Mn-za-m/; # im Usenet häufig verwandte ROT13-Chiffre
Als Rückgabewert der Operation wird die Anzahl der gefunden Zeichen verwendet. Damit gibt es auch die Möglichkeit, mit tr///
Zeichen in einem String zu zählen. Dazu verwendet man die gleichen Sequenzen für SUCHEN und ERSETZEN, oder die Liste der ersetzenden Zeichen wird einfach leergelassen:
# Zählt die Vokale im String
$anzahlVokale = ($string =~ tr/AEIOUaeiou//);