01.01.02 – Komplexe Suchmuster

3. Teil

So, das war ja einfach. Allerdings auch wenig interessant. Wenn sich die Möglichkeiten von Regexen in den einfachen Suchmustern erschöpften, wäre es wohl kaum wert, sie in einem Tutorial vorzustellen.

Es muss also mehr geben! Fangen wir mal an:

3.1. Zeilengrenzen

Außer einfach den Text irgendwo zu suchen, kann man ein Regex veranlassen, an bestimmten Stellen zu suchen. Dafür gibt es Metazeichen: ^ $ (Ja, liebe Experten, lasst uns für den Anfang die Zeichenkette als eine Zeile betrachten, ok?)

Konkret: „^dies oder das“. Die Zeichenfolge wird nur gefunden, wenn das Wort „dies“ unmittelbar am Anfang einer Zeile steht und von “ oder das“ gefolgt wird.

Oder: „Das ist das Ende$“ wird genau nur am Zeilenende gefunden. Es ist egal, was vor der Zeichenfolge erscheint, aber „Das ist das Ende“ muss mit der Zeile enden.

Diese beiden Metazeichen sollten unter anderem eingesetzt werden, um das Regex zu beschleunigen. Warum wird das Regex schneller? Nehmen wir mal wieder unser Beispiel „^dies oder das“ und wenden das auf den Text ‚Es war einmal‘ an: die Regex-Maschine prüft zunächst, ob es sich bei dem ersten Zeichen um den Zeilenanfang handelt: das liefert WAHR zurück. Dann prüft die Maschine das nächste Zeichen darauf, ob es ein „d“ ist, was natürlich fehlschlägt. Die Suche wird sofort an dieser Stelle mit dem Ergebnis FALSCH abgebrochen. Ohne das ^-Zeichen hätte die Maschine das zweite, dritte, vierte usw. Zeichen geprüft und versucht „dies oder das“ im String zu finden, um am Ende festzustellen, dass die Zeichenfolge nicht existiert.

3.2. Wortgrenzen

Neben den Zeilengrenzen sind Wortgrenzen interessant, allerdings selten beachtet. Mit „\b“ wird das Regex veranlasst, den betreffenden String mit einer Wortgrenze zu suchen: „\bdies oder das“

Kennen wir schon, nicht wahr? Das Beispiel vom Anfang, in dem diese beiden Wörter in ‚Paradies oder das Weib‘ gefunden wurde, würde nun nicht mehr gefunden. Ich erinnere mich an eine Diskussion in einer Mailingliste vor längerer Zeit, in der gefragt wurde, was der Sinn des \b sein solle, da man doch auch die Wortgrenze über ein Leerzeichen ermitteln könne. Das ist nur bedingt richtig, denn Wörter enden auch an Interpunktionszeichen: „er “ findet in der Tat ‚der Regen‘, aber ‚wieder.‘ als Satzabschluss würde nicht gefunden. Dies lässt sich mit „er\b“ umgehen.

Natürlich ist das auch wieder negierbar: „\B“ bedeutet, dass diese Zeichenkette nicht an Wortgrenzen gefunden werden soll.

Auch hierzu ein Beispiel: „Re\B.“ Also, es soll die Zeichenkette ‚Re‘, aber nur, wenn sie nicht die Wortgrenze darstellt, aber von einem beliebigen Zeichen gefolgt wird. Wenden wir das mal auf ‚Re: oder Reply:‘ an. Der Test im Tester ergibt ‚Rep‘. Tauscht mal \B gegen \b aus und es wird ‚Re:‘ gefunden. Alles klar?

3.3. Alternativen

Wir erinnern uns an das erste Regex „dies oder das“ in diesem Kurs? Dazu schrieb ich den eigentlich überflüssigen Zusatz, dass dies nicht bedeutet, das Regex würde entweder nach „dies“ oder nach „das“ suchen. Na ja, so überflüssig war das nicht: ich wollte doch schließlich irgendwas haben, was eine Einleitung für diesen Abschnitt bietet *g*. Genau um diese Alternativen, nämlich dem ODER, dreht es sich nun.

Um nach alternativen Zeichenketten zu suchen, bieten die Regexe ein besonderes Zeichen: „|“ den Strich, den einige von euch auch als Pipe-Symbol kennen. Konkretes Beispiel: „dies|das“ Das Regex prüft, ob es die Zeichenkette ‚dies‘ findet. Wenn es diese nicht findet, sucht es nach der zweiten Alternative.

Was passiert aber, wenn beide Alternativen im Text enthalten sind? Nun, man sollte glauben, dass die Alternative gefunden wird, die als erste im Regex steht. Dem ist aber nicht so! Vielmehr wird als gefundener Text das erste Auftreten einer der Alternativen in der Zeichenkette zurückgegeben!

Beispiel:

Gegeben sei das Regex „das|dies|jenes“ und die Zeichenkette ‚Es war jenes Auto, das wir diesseits der Böschung geborgen haben‘ (Die Beispielzeichenketten sind nicht immer hochliterarisch, aber sie sollten dennoch helfen ;-)) Als gefundener Text wird das ‚jenes‘ zurückgeliefert. Probiert es mal im Regex-Tester aus. Diese Besonderheit wird noch mal für uns interessant, wenn wir zu dem Abschnitt kommen, in welchem wir uns Textstücke mit dem Regex merken wollen.

Solche Alternativen sind auch kombinierbar: „^aw:|^wg:|^fwd:“ bedeutet, das nach diesen drei Möglichkeiten alternativ gesucht wird. In allen drei Fällen wird nach dem Zeilenanfang gesucht und hinter jeder Zeichenkombination steht ein Doppelpunkt . Ihr habt recht: das müsste man vereinfachen können. Und genau wie in der Mathematik kann ich hier Klammern setzen und das Regex vereinfachen und beschleunigen: „^(aw|wg|fwd):“

Derartige Vereinfachungen müssen nicht immer einfacher zu lesen sein: „d(ie|a)s“ wäre die Vereinfachung zu unserem Eingangsbeispiel in diesem Abschnitt, was zwar richtig wäre, aber nicht gerade den Lesefluß unterstützt 😉

3.4. Besondere Klassen

Einige der Zeichenklassen haben wir schon oben kennen gelernt. Ich möchte hier noch ein paar von unterschiedlicher Bedeutung nachschieben.

In fast jedem Regex werdet ihr die Zeichenklasse „\s“ finden. Sie steht für so genannte Whitespace-Zeichen, also alle Zeichen, die einen weißen Raum auf den Bildschirm zeigen: Leerzeichen, Tabulatoren, die Befehle ‚Neue Zeile‘, ‚Wagenrücklauf‘, ‚Blattvorschub‘. Es wird genügen, sich zu merken, dass jede leere Stelle in einer Zeichenkette hiervon gefunden wird. Natürlich kann man dies auch negieren: „\S“ passt auf jedes andere Zeichen, das eben nicht weißen Platz beansprucht.

„\A“ wird seltener eingesetzt: es passt auf den Anfang einer Zeichenkette. Dies ist nicht der Beginn einer Zeile, denn das hätten wir mit „^“ gesucht. Analog hierzu gibt es „\Z“: dieses findet das Ende der Zeichenkette oder anders gesagt: es findet das Zeichen unmittelbar vor dem ‚Neue Zeile‘-Befehl. Und erneut: dies ist nicht das Ende einer Zeile, denn das hätten wir mit „$“ gesucht. Wir werden den Unterschied später sehen, wenn wir zu den Optionen kommen. Sorry, aber ihr müsst euch etwas gedulden *g*.

3.5. Übersicht über dieses Kapitel

Dieser Abschnitt hat uns ein paar neue Möglichkeiten zur Suchmusterdefinition gezeigt:

  • Zeilengrenzen haben eigene Suchkriterien: ^ für den Zeilenanfang und $ für das Zeilenende.
  • Für Wortgrenzen gibt es „\b“ mit dem nach Zeichenketten gesucht werden kann, die den Abschluss eines Wortes bilden. „\B“ steht für Zeichenketten, die nicht Wortgrenzen darstellen.
  • Man kann Zeichenketten als Alternativen definieren. Dazu trennt man jede Alternative mit dem Strich „|“ ab. Gleiche Zeichen an gleichen Stellen innerhalb der Alternativen, lassen sich wie in der Mathematik vor oder hinter eine Klammer ziehen: „^(Re|Aw|Fwd)“ Für alle drei Alternativen gilt, dass sie am Anfang einer Zeile stehen müssen.
  • Leerzeichen oder Tabulatoren werden als so genannte Whitespace-Character bezeichnet, für die ein spezielles Suchmuster existiert: \s Negiert schreibt man für diese Gruppe \S
  • Auch Anfang und Ende einer Zeichenkette sind suchbar: \A und \Z

Aufgaben:

1. Gegeben: „(R.:$|^R.:)“ sowie die Zeichenkette ‚Ra: oder Re:‘. Was wird gefunden?

2. Ich will die Zeichenkette ‚Foo:‘ am Zeilenanfang finden; allerdings auch dann, wenn ihr noch der etwas folgt, also zum Beispiel ‚Foobar:‘. Wie muss das Regex nach unserem bisherigen Stand aussehen?

3. Wir versuchen mal, ein Regex zu basteln, das die Zeichenfolge ‚Der‘ am Anfang einer Zeile oder ‚)‘ am Zeilenende findet.

4. Was bedeuten die folgenden Suchmuster?

a)“^“
b)“^x$“
c)“^$“

Im ersten Beispiel wird ‚Ra:‘ gefunden. Das hatten wir erwartet: gefunden wird die Alternative, die in der Zeichenkette zuerst erscheint.

Hoppla, das zweite Beispiel ist ja schon etwas anspruchsvoller: „^(Foo|Foobar):“ Ok, man kann es vereinfachen: „^Foo(|bar):“ Um nach nichts hinter dem ‚Foo‘ und vor dem ‚:‘ zu suchen, haben wir auch einfach nichts als erste Alternative eingetragen. Wir werden im folgenden Kapitel feststellen, dass man dieses auch anders suchen kann.

Drittes Beispiel:

„(^Der|\)$)“ wäre die Lösung. Ihr hattet hoffentlich an den Backslash vor der Klammer gedacht? Prima, dann habt ihr aufgepasst *g*. Probiert es mal im Tester aus. Ihr werdet erkennen, dass das Regex aus der Zeichenkette ‚Der Regen fällt (was aber keinen interessiert)‘ nur genau ‚Der‘ findet, denn da war die Bedingung erfüllt. Ändert den Zeilenanfang, indem ihr irgendetwas eintragt und schon wird das ‚Der‘ nicht mehr gefunden. Stattdessen wird die abschließende runde Klammer gefunden.

Viertes Beispiel:

Das erste Muster sucht nach allen Texten, die einen Zeilenanfang haben oder am Zeilenanfang beginnen. Es werden also alle Strings, sogar der leere String gefunden!

Das zweite Muster sucht nach Zeilen, in denen nur der Buchstabe x steht, direkt nach Zeilenbeginn und am Ende der Zeile.

Und das letzte Muster sucht einfach nach allen Texten, die Zeilenanfang und -ende haben, aber nichts dazwischen. Es werden leere Zeilen gesucht.

weiter