Reguläre Ausdrücke: Der einfache Weg, Zeichenfolgen zu beschreiben
Reguläre Ausdrücke (engl. regular expressions) auch Regex genannt, sind Zeichenketten auf Basis syntaktischer Regeln, die es ermöglichen, Zeichenfolgen zu beschreiben. Als solche sind sie Bestandteil der regulären Sprachen – einer Untergruppe der formalen Sprachen, die insbesondere in der Informationstechnik und speziell in der Software-Entwicklung, von großer Bedeutung ist.
Was ist ein regulärer Ausdruck?
Reguläre Ausdrücke (engl. regular expressions) sind die Beschreibungseinheiten regulärer Sprachen, die zu den sogenannten formalen Sprachen zählen. Sie sind ein zentrales Instrument der theoretischen Informatik, die unter anderem die Grundlage für die Entwicklung und Ausführung von Computerprogrammen sowie den Bau der dafür erforderlichen Compiler bildet. Aus diesem Grund finden reguläre Ausdrücke – die häufig auch als Regex bezeichnet werden und auf klar definierten, syntaktischen Regeln basieren – insbesondere in der Software-Entwicklung Verwendung.
Zu jedem regulären Ausdruck existiert ein sogenannter endlicher Automat (auch Zustandsmaschine genannt), der die vom Ausdruck spezifizierte Sprache akzeptiert und sich mithilfe der Thompson-Konstruktion aus einem regulären Ausdruck entwickeln lässt. Auf der anderen Seite gibt es für jeden endlichen Automaten auch einen regulären Ausdruck, der die vom Automaten akzeptierte Sprache beschreibt. Dieser lässt sich wahlweise durch den Kleene-Algorithmus oder das Eliminieren von Zuständen erzeugen.
Ein Automat ist ein Verhaltensmodell, das aus Zuständen, Zustandsübergängen und Aktionen besteht. Als endlich wird er dann bezeichnet, wenn die Menge an Zuständen, die er annehmen kann, endlich (also beschränkt) ist.
Ein bekanntes Beispiel für die Nutzung regulärer Ausdrücke in der Informationstechnik ist die Suchen-und-Ersetzen-Funktion von Texteditoren, die der Computerpionier Ken Thompson – einer der Entwickler des UNIX-Betriebssystems – erstmals in den 1960er-Jahren in den zeilenorientierten Editor QED und später in dessen Nachfolger ed implementiert hatte. Diese Funktion ermöglicht es, bestimmte Zeichenfolgen in Texten zu suchen und – sofern gewünscht – durch eine beliebige andere Zeichenfolge zu ersetzen.
Wie funktioniert ein regulärer Ausdruck?
Ein regulärer Ausdruck kann wahlweise ausschließlich durch normale Zeichen (z. B. abc) oder durch eine Kombination von normalen Zeichen und Metazeichen (z B. ab*c) gebildet werden. Die Metazeichen haben dabei die Aufgabe, bestimmte Zeichenkonstruktionen bzw. -anordnungen zu beschreiben – also beispielsweise, ob ein Zeichen am Zeilenanfang stehen soll oder ob ein Zeichen exakt einmal oder auch häufiger bzw. seltener vorkommen darf bzw. soll. Die beiden genannten Beispiele regulärer Ausdrücke funktionieren zum Beispiel folgendermaßen:
abc: Das einfache Regex-Muster abc erfordert eine exakte Übereinstimmung. Es wird also nach Zeichenketten gesucht, in denen die Zeichen „abc“ nicht nur allesamt enthalten sind, sondern auch in exakt dieser Reihenfolge auftreten. Die vom Ausdruck geforderte Übereinstimmung bieten daher sowohl die Frage „Kennst du das abc?“ als auch der Satz „Das müssen wir noch abchecken.“.
ab*c: Reguläre Ausdrücke mit Sonderzeichen funktionieren hingegen etwas anders, da nicht nur nach exakten Übereinstimmungen, sondern auch speziellen Szenarios gesucht wird. Das Sternchen (auch „Asteriks“ genannt) sorgt im vorliegenden Fall dafür, dass zwar nach Zeichenketten gesucht wird, die mit dem Buchstaben „a“ beginnen und mit dem Buchstaben „c“ enden – dazwischen kann sich allerdings eine beliebige Zahl des Buchstaben „b“ befinden, sodass eine Übereinstimmung in „abc“ als auch in den Zeichenketten „abbbbc“ und „cbbabbcba“ vorliegt.
Jeder Regex lässt sich darüber hinaus mit einer konkreten Aktion verknüpfen – wie zum Beispiel dem bereits genannten „Ersetzen“. Diese Aktion wird überall dort ausgeführt, wo der jeweilige reguläre Ausdruck zutrifft – also überall dort, wo wie in den geschilderten Beispielen eine entsprechende Übereinstimmung vorliegt.
Welche Herausforderungen sind mit dem Einsatz regulärer Ausdrücke verbunden?
Wer mit Regex-Anweisungen arbeiten möchte, der hat dabei eine Menge Freiheiten, denn zu jeder Aufgabenstellung, die man mit einem regulären Ausdruck lösen möchte, gibt es immer mehrere Lösungsoptionen. Dass sich ein gewünschtes Resultat auf verschiedenen Wegen erzielen lässt, ist allerdings nicht immer ein Vorteil:
So können Sie die Anweisungen zum Beispiel sehr allgemein halten, um das gewünschte Ziel in jedem Fall zu erreichen – wollen Sie ein möglichst akkurates Ergebnis erhalten, kommen Sie allerdings nicht darum herum, ein spezifisches Regex-Muster zu formulieren. Auch ein genereller Blick auf die Länge ist empfehlenswert: Je kompakter ein regulärer Ausdruck ist, desto geringer fällt auch seine Verarbeitungsdauer aus. Dabei sollten Sie allerdings wiederum nicht die Lesbarkeit aus den Augen verlieren. Denn falls Sie verwendete reguläre Ausdrücke später ändern möchten, stellt es ein großes Hindernis dar, wenn die ursprünglichen Anweisungen zu kompliziert und darüber hinaus unkommentiert sind.
Generell gilt es bei der Erstellung regulärer Ausdrücke also, das optimale Verhältnis zwischen Kompaktheit und Spezifität zu finden.
Welche syntaktischen Regeln gelten für reguläre Ausdrücke?
Wie bereits erwähnt, lassen sich reguläre Ausdrücke in verschiedenen Sprachen – wie beispielsweise Perl, Python, Ruby, JavaScript, XML oder HTML – einsetzen, wobei der Nutzen bzw. die Funktion sehr verschieden sein kann. In JavaScript sind Regex-Muster beispielsweise bei den String-Methoden search(), match() oder replace() im Einsatz, während die Ausdrücke in XML-Dokumenten der Beschränkung von Element-Inhalten dienen. In puncto Syntax gibt es zwischen den einzelnen Programmier- und Auszeichnungssprachen allerdings kaum Unterschiede, wenn es um reguläre Ausdrücke geht:
So kann ein regulärer Ausdruck unabhängig von der Sprache, in der er verwendet wird, aus bis zu drei Teilen bestehen:
Pattern (Suchmuster) | Das zentrale Element ist das Pattern, also das generelle Suchmuster. Wahlweise kann es sich – wie im vorigen Abschnitt bereits erläutert – ausschließlich aus einfachen Zeichen oder aus einer Kombination von einfachen Zeichen und Sonderzeichen zusammensetzen. |
---|---|
Delimiter (Trennzeichen) | Anfang und Ende des Patterns werden durch Delimiter gekennzeichnet. Grundsätzlich kommen als Trennzeichen alle nicht-alphanumerischen Zeichen (mit Ausnahme des Backslashs) in Frage. PHP sieht beispielsweise wahlweise Hashtags (#pattern#), Prozentzeichen (%pattern%), Pluszeichen (+pattern+) oder Tilden (~pattern~) als Delimiter vor. In den meisten Sprachen werden allerdings gerade Anführungszeichen ("pattern") oder Slashes (/pattern/) verwendet. |
Modifier (Modifikator) | Modifier können einem Suchmuster angehängt werden, um den regulären Ausdruck zu modifizieren. So gibt es beispielsweise den Modifikator i, der die Case-Sensitivity aufhebt. Diese sorgt dafür, dass Groß- und Kleinschreibung eine Rolle spielen und standardmäßig für alle regulären Ausdrücke gelten. |
Zu den typischen, syntaktischen Sonderzeichen, die Pattern um bestimmte Optionen erweitern können, zählen folgende:
Syntaktische Regex-Sonderzeichen | Funktion |
[] | Ein Paar eckiger Klammern kennzeichnet eine Zeichenklasse, die immer für ein einziges Zeichen in einem Suchmuster steht. |
() | Ein Paar runder Klammern kennzeichnet eine Zeichengruppe, die aus einem oder mehreren Zeichen bestehen und ineinander geschaltet werden können. |
- | Fungiert als Bereich-Angabe (von [… ] bis […]), wenn es zwischen zwei normalen Zeichen steht |
^ | Suche auf den Anfang einer Zeile beschränken (weitere Funktion: Negator in Zeichenklassen) |
$ | Suche auf das Ende einer Zeile beschränken |
. | Steht für jedes beliebige Zeichen |
* | Die Anzahl des Zeichens, der Klasse oder Gruppe vor einem Stern kann beliebig sein (null eingeschlossen). |
+ | Zeichen, Klasse oder Gruppe vor einem Pluszeichen muss mindestens einmal vorhanden sein. |
? | Zeichen, Klasse oder Gruppe vor einem Fragezeichen ist optional und darf maximal einmal vorkommen. |
| | Kennzeichnet zwei oder mehrere Alternativen |
{n} | Voranstehendes Zeichen oder voranstehende Klasse oder Gruppe kommt exakt n-mal vor. |
{n,m} | Voranstehendes Zeichen, voranstehende Klasse oder Gruppe kommt mindestens n-mal und höchstens m-mal vor. |
{n,} | Voranstehendes Zeichen, voranstehende Klasse oder Gruppe kommt mindestens n-mal oder häufiger vor. |
\b | Wortgrenze bei der Suche berücksichtigen |
\B | Wortgrenze bei der Suche ignorieren |
\d | Beliebige Ziffer; Kurzschreibweise für die Zeichenklasse [0-9] |
\D | Beliebige Nicht-Ziffer; Kurzschreibweise für die Zeichenklasse [^0-9] |
\w | Beliebiges alphanumerisches Zeichen; Kurzschreibweise für die Zeichenklasse [a-zA-Z_0-9] |
\W | Beliebiges nicht-alphanumerisches Zeichen; Kurzschreibweise für die Zeichenklasse [^\w] |
Tutorial: Die Möglichkeiten regulärer Ausdrücke anhand von Beispielen erklärt
Nachdem die vorangegangenen Abschnitte dieses Artikels die Regex-Grundlagen zusammengefasst haben, soll das nachfolgende Tutorial die Funktionsweise der praktischen Zeichenketten veranschaulichen. Dabei werden die verschiedenen Möglichkeiten und syntaktischen Kniffe anhand konkreter Beispiele für reguläre Ausdrücke veranschaulicht – sowohl an einfachen als auch an komplexen Ausdrücken.
Einelementige reguläre Ausdrücke
Die einfachste Regex-Form ist ein Suchmuster, das lediglich ein einzelnes Element als Treffer vorsieht. Sofern Sie nicht ein konkretes Element suchen, lässt sich ein solcher einelementiger regulärer Ausdruck zum Beispiel problemlos mithilfe einer Zeichenklasse definieren. Folgender Ausdruck erlaubt wahlweise die Ziffern „1“, „2“, „3“, „4“, „5“, „6“ oder „7“ als mögliches Resultat:
[1234567]
Da die Zahlen in diesem Fall direkt aufeinanderfolgen, wäre auch folgende, vereinfachte Schreibweise möglich:
[1-7]
Soll der reguläre Ausdruck dahingehend geändert werden, dass die Ziffer „4“ von der Suche ausgenommen wird, können Sie ebenfalls die einfachere Variante mit dem Minuszeichen nutzen:
[1-35-7]
Die einzelnen Zeichen eines Regex-Patterns werden nicht durch Leerzeichen voneinander getrennt.
Mehrelementige reguläre Ausdrücke
Auch bei einem mehrelementigen regulären Ausdruck können Sie mit Zeichenklassen arbeiten, um eine Auswahl verschiedener Treffer zu ermöglichen. Soll der Ausdruck beispielsweise zwei Elemente erfassen, für die unterschiedliche Resultate denkbar sind, reihen Sie einfach zwei entsprechende Zeichenklassen aneinander:
[1-7][a-c]
Auf das erste Element, eine Zahl zwischen „1“ und „7“, folgt also einer der Buchstaben „a“, ein „b“ oder ein „c“. Wie bereits erwähnt, ist hierbei die Kleinschreibung obligatorisch. Bevor Sie sich an dieser Stelle aber schon mit Modifiern auseinandersetzen, können Sie bereits mit der folgenden, kleinen Änderung des Ausdrucks Großbuchstaben einbeziehen:
[1-7][a-cA-C]
Reguläre Ausdrücke mit optionalen Elementen
Unabhängig davon, ob Sie mehrere Elemente innerhalb eines einzelnen regulären Ausdrucks oder mithilfe mehrerer Zeichengruppen suchen, kann es sein, dass bestimmte Elemente nur unter bestimmten Voraussetzungen enthalten sein müssen bzw. können. Denkbar wäre dies zum Beispiel bei einem regulären Ausdruck, der alle Hausnummern herausfiltern soll. Solchen Fällen, in denen die Hausnummer aus einer einzelnen Ziffer gebildet wird, stehen dabei unter Umständen solche Treffer gegenüber, in denen die Nummer sich aus zwei oder sogar drei Ziffern zusammensetzt. Zudem gibt es Adressen, bei denen der Hausnummer ein Buchstabe als Zusatz angehängt wird. Abdecken lässt sich diese Gesamtmenge an möglichen Kombinationen durch folgende Regex-Anweisungen:
[1-9][0-9]?[0-9]?[a-z]?
Das einzige Pflichtelement dieses Suchmusters ist eine Zahl zwischen „1“ und „9“. Optional können zwei Ziffern zwischen „0“ und „9“ sowie ein beliebiger Buchstabe folgen, was jeweils durch das nachfolgende Fragezeichen gekennzeichnet ist.
Während die Konstruktion für dreistellige Nummern plus zusätzlichem Buchstaben noch recht übersichtlich ist, sähe dies bei Nummern mit bis zu zehn Ziffern deutlich anders aus. In diesem Fall empfiehlt sich der Einsatz geschweifter Klammern, wie in folgendem Beispiel regulärer Ausdrücke:
[1-9][0-9]{0,9}
Wie im vorigen Beispiel ist an erster Stelle eine Zahl zwischen „1“ und „9“ gefordert – nachfolgen können allerdings wahlweise keine oder bis zu neun Ziffern zwischen „0“ und „9“, sodass sich das Suchresultat also aus bis zu zehn Ziffern zusammensetzen kann.
Regulärer Ausdruck mit beliebig häufigen Wiederholungen
In den bisherigen Beispielen für ein- und mehrelementige Ausdrücke war sowohl die minimale als auch die maximale Anzahl an Zeichen bekannt. Doch natürlich existieren auch Szenarien, in denen die Zeichenmenge eines Regex im Vorhinein nicht exakt festgelegt werden soll. Die notwendigen Parameter sind dann das Sternchen- und das Pluszeichen, die es ermöglichen, beliebige Wiederholungen eines Zeichens bzw. einer Zeichenklasse oder -gruppe zuzulassen. So lassen sich alle Zeichenketten mit einer beliebigen Anzahl an Ziffern (auch „null“) beispielsweise mit folgendem regulären Ausdruck erfassen:
[0-9]*
Gleiches gilt, wenn eine konkrete Zeichenkombination gesucht wird, bei der ein (oder mehrere) Zeichen beliebig häufig auftreten können. Wie in folgendem Beispiel:
ab*
Mögliche Treffer sind in diesem Fall sowohl das Wort „anfangen“ als auch „abladen“ und „abbrechen“. Soll ersteres Resultat ausgeschlossen werden bzw. das spezifizierte Zeichen mindestens einmal vorkommen, ist stattdessen das Pluszeichen zu verwenden:
ab+
Zeichenklassen negieren
Wenn Sie reguläre Ausdrücke mit Zeichenklassen verwenden wollen, die grundsätzlich für ein bzw. mehrere beliebige Zeichen stehen, dabei aber ein bzw. mehrere bestimmte Zeichen als Treffer ausschließen, benötigen Sie den Negator „^“ (Dachzeichen). Dieser steht immer innerhalb der Klammern einer Zeichenklasse und gilt auch nur innerhalb dieser Klammern. Ein gutes Beispiel für eine negierte Zeichenklasse bietet folgende Anweisung:
H[^u]nd
Bei dem zweiten Zeichen kann es sich in diesem Fall also um ein beliebiges Zeichen außer „u“ handeln, weshalb das Wort „Hand“ die erforderlichen Übereinstimmungen bietet. Das Wort „Hund“ bietet diese jedoch nicht, weshalb es auch nicht auf den regulären Ausdruck zutrifft.
Platzhalter
Reguläre Ausdrücke ermöglichen auch die Arbeit mit Platzhaltern, die wahlweise für ein, mehrere oder kein Zeichen (je nach verwendetem Metazeichen) inmitten eines Suchmusters stehen. Der Platzhalter wird dabei durch einen Punkt erzeugt, den Sie mit den zuvor aufgezählten Sonderzeichen für Wiederholungen kombinieren, wenn ein anderes Resultat als ein einzelnes Zeichen gewünscht ist. Derartige reguläre Ausdrücke ermöglichen es zum Beispiel, eine Datenbank nach einer Person zu durchsuchen, von der Sie zwar den Vor- und Nachnamen kennen, aber nicht wissen, ob diese noch mit einem zweiten Vornamen eingetragen ist:
Max .*Mustermann
Mögliche Treffer sind in diesem Fall sowohl „Max Erik Mustermann“ (sowie jede andere Kombination mit Zweitnamen) oder „Max E. Mustermann“ als auch „Max Mustermann“. Sollen ausschließlich Varianten mit einem zweiten Vornamen berücksichtigt werden, nutzen Sie statt des Sternchens ein Pluszeichen:
Max .+Mustermann
Ein gutes Beispiel für den sinnvollen Einsatz eines Platzhalters für ein einzelnes Zeichen ist folgendes Suchmuster, das sowohl mit „Hase“ als auch „Hose“ übereinstimmt:
H.se
Alternativen
Sie haben die Möglichkeit, reguläre Ausdrücke so zu formulieren, dass es zwei oder mehrere Alternativen für eine Übereinstimmung gibt. Die Alternativen gilt es dabei, durch einen senkrechten Strich zu trennen – wie in folgendem Beispiel:
Baum|Blume
Eine Übereinstimmung bieten also sowohl „Baum“ als auch „Blume“.
Alternativen lassen sich auch innerhalb von Wörtern bzw. Zeichenfolgen formulieren, indem man auf Gruppen zurückgreift:
(Sonn|Mon|Diens|Donners|Frei|Sams)tag|Mittwoch
In diesem Beispiel ist jeder Wochentag ein potenzieller Treffer, wobei alle als Alternative angebotenen Wochentagnamen, die auf „tag“ enden dank der Gruppierung durch die runden Klammern auch in der abgekürzten Form korrekt erfasst werden.
Gruppen
Zeichengruppen wie im Beispiel des voranstehenden Abschnitts zählen wie Zeichenklassen zu den Strukturelementen regulärer Ausdrücke. Sie lassen sich durch ein Paar runder Klammern definieren und stehen grundsätzlich für ein Pattern, das aus einem oder mehreren Zeichen besteht – genau genommen ist also jeder Regex eine Gruppe, wobei die Kennzeichnung durch die Klammern in diesem Fall aber entfällt. Innerhalb der Ausdrücke gewähren Gruppen die Möglichkeit, Operatoren wie das Trenn- oder die Wiederholungszeichen (Plus und Sternchen) auf einen gewünschten Teilausdruck anzuwenden:
ab(cd)+
Die gewünschte beliebige Wiederholung gilt in diesem Fall also für die Zeichengruppe „cd“, während sie bei der gleichen Schreibweise ohne Klammern nur für das „d“ gelten würde. Innerhalb eines Regex gibt es keinerlei Einschränkungen hinsichtlich der Menge an enthaltenen Gruppen.
Verschachtelungen
Es kann nicht nur eine beliebige Anzahl an Gruppen innerhalb eines regulären Ausdrucks existieren, es können auch beliebig viele Gruppen ineinander geschachtelt werden, um komplexe Beziehungen zwischen einfachen Zeichen und Sonderzeichen auch ohne unnötig lange Zeichenketten auszudrücken. Ein Beispiel hierfür ist folgendes Regex-Muster, das die vier Automodelle „VW Golf“, „VW Polo“, „Fiat Punto“ oder „Fiat Panda“ als mögliche Treffer hat:
(VW (Golf|Polo)|Fiat (Punto|Panda))
Wortgrenzen
Sollen Wortgrenzen, also der Anfang bzw. das Ende einer alphanumerischen Sequenz, bei der Anwendung eines regulären Ausdrucks berücksichtigt werden, muss man dies per Metazeichen spezifizieren. Viele Sprachen verwenden hierfür die Kombination „\b“, die wahlweise dem Suchmuster vorangestellt, angehangen oder vorangestellt und angehangen werden kann.
Erstere Variante schreibt vor, dass die Suchsequenz am Wortanfang steht:
\bein
Eine Übereinstimmung für diesen regulären Ausdruck bietet zum Beispiel das Wort „einfach“. Das Wort „Bein“ ist hingegen als Treffer ausgeschlossen, da den gesuchten Zeichen der Buchstabe „B“ voransteht. Um den Spieß umzudrehen, nutzen Sie Variante Nummer Zwei und hängen die Sonderzeichen an:
ein\b
Mit der dritten Option machen Sie schließlich beide Wortgrenzen zur Voraussetzung, was im Fall des verwendeten Beispiels den unbestimmten Artikel (bzw. das Pronomen/Zahlwort/Adverb) „ein“ zum einzigen möglichen Treffer macht:
\bein\b
Metabedeutung von Sonderzeichen aufheben
Im voranstehenden Abschnitt hat der Backslash dafür gesorgt, dass das nachfolgende „b“ nicht als Buchstabe, sondern als Metazeichen gewertet wird. Kombiniert man ihn mit Zeichen, die standardmäßig zu den syntaktischen Regex-Sonderzeichen zählen, hat er genau die entgegengesetzte Wirkung – das Zeichen wird als gewöhnlicher Literal behandelt. Dank dieser Möglichkeit können Sie mit einem regulären Ausdruck auch problemlos nach einem konkreten Datum suchen:
11\.10\.2019
Das Datum „11.10.2019“ stimmt in diesem Fall als einzige Zeichenkette mit den geforderten Suchkriterien überein. Ohne den gesetzten Backslash würden die beiden Punkte als Platzhalter für ein beliebiges Zeichen gewertet werden, weshalb dann auch Resultate wie „1101092019“ oder „11a10b2019“ möglich wären.
Gierige reguläre Ausdrücke „entschärfen“
Der Einsatz von Quantoren („?“, „+“, „*“, „{}“) sorgt standardmäßig dafür, dass ein Ausdruck „gierig“ ist und nach der größtmöglichen Übereinstimmung sucht. Da dieses Verhalten aber nicht immer gewünscht ist, lassen sich Quantoren in einem regulären Ausdruck so spezifizieren, dass dessen „Gier“ eingedämmt wird. Deutlicher wird dieser Modifizierungsprozess durch folgendes Beispiel:
A.*B
Angewendet auf die Zeichenfolge „ABCDEB“ würde dieser gierige Ausdruck die Suche nicht nach „AB“ stoppen, sondern die gesamte Zeichenfolge als Treffer erfassen. Soll die Suche hingegen bereits nach dem ersten gefundenen „B“ abbrechen, bedarf es der angesprochenen Modifizierung. In vielen Sprachen (u.a. Perl, Tcl, HTML) wird Quantoren zu diesem Zweck ein Fragezeichen nachgestellt:
A.*?B
Alternativ lässt sich der ursprüngliche, gierige Ausdruck auch durch folgenden gleichwertigen, „nicht-gierigen“ Ausdruck ersetzen, um zum gleichen Ergebnis zu kommen:
A[^B]*B
Die Einschränkung gieriger regulärer Ausdrücke macht die Verarbeitung eines Suchmusters komplizierter und ist daher mit einer erhöhten Suchdauer verknüpft.