Reguläre Ausdrücke

Zweck

  • Reguläre Ausdrücke (regular expressions) sind mächtiges Werkzeug
    • testen ob ein String ein Muster enthält
    • Teile aus einem String extrahieren
    • Teile eines Strings ersetzen
  • Bestehen aus
    • einem Muster (pattern),
    • das auf einen String angewendet wird
  • Wenn das Muster im String vorkommen, sagt man es matched

Reguläre Ausdrücke können die Arbeit mit Strings und Textdaten erheblich vereinfachen. Sie werden von JavaScript als Teil der Sprache unterstützt, müssen also anders als in Python oder Java nicht über spezielle Bibliotheken oder Methoden realisiert werden.

Technisch gesehen basieren Reguläre Ausdrücke auf den Deterministischen Endlichen Automaten (DEA), d. h. sie sind eine spezielle Form, einen solchen Automaten zu beschreiben. Hat man einen Regulären Ausdruck erstellt kann man testen, ob ein gegebener String matched, d. h. ob der durch ihn beschriebene DEA den String akzeptiert oder nicht. Wenn der Ausdruck matched, kann man die Position im String bestimmen, an der dies erfolgt ist und basierend darauf Teile aus dem String extrahieren oder auch ersetzen,

Ein Regulärer Ausdruck wird durch ein Muster beschrieben, das eine spezielle Syntax besitzt.

Grundlagen regulärer Ausdrücke

  • RegEx-Literale werden in / eingeschlossen, z. B. /abc/
  • Alle Zeichen, bis auf . | ( ) [ ] { } + \ ^ $ * ? matchen sich selbst
  • . steht für jedes beliebige Zeichen
  • Können mit match auf einen String angewandt werden
    • Ergebnis ist ein Objekt mit der Position des Matches unter .index oder
    • null, wenn die RegEx nicht matched
"dog and cat".match(/cat/).index; // => 8
"catch".match(/cat/).index;       // => 0
"Cat".match(/cat/);               // => null
"nyan cat".match(/cat/).index;    // => 5

if ("dog and cat".match(/cat/)) { log("Katze gefunden"); }

Da Reguläre Ausdrücke ein fester Bestandteil der Sprache sind, gibt es für sie in JavaScript entsprechende Literale, mit denen man sie direkt im Quelltext angeben kann. Diese Literale werden standardmäßig durch das Zeichen / eingeschlossen.

Die match-Funktion kann benutzt werden, um einen Regulären Ausdruck auf einen String anzuwenden.

Ein Regulärer Ausdruck ist selbst eine Zeichenkette, in der jedes Zeichen (mit einigen Ausnahmen) zuerst einmal sich selbst matched, z. B. matched /cat/ genau den String „cat“. Sobald der Reguläre Ausdruck gefunden wurde (matched), wird als Ergebnis die Position zurückgegeben. Matched er nicht, so ist das Ergebnis nil. Kommt der Reguläre Ausdruck mehrfach im String vor, ist das Ergebnis von match der erste Treffer. Will man weitere Treffer im String finden, muss man diese entsprechende Methoden anfordern, die match-Methode hilft dann nicht weiter.

Neben den Buchstaben, die sich selbst matchen, gibt es eine Reihe von speziellen Zeichen, die z. B. als Wildcard oder Gruppierungszeichen dienen. Diese geben Regulären Ausdrücken ihre Flexibilität. Reguläre Ausdrücke sind relativ komplex, sodass hier nur ein kurzer Überblick gegeben werden kann.

  • . = ein beliebiges Zeichen
  • | = oder
  • (, ) = umschließt eine Gruppe
  • [, ] = umschließt eine Zeichenklasse
  • { } = umschließt Wiederholungen
  • * = 0 oder mehr Vorkommen
  • + = 1 oder mehr Wiederholungen
  • ? = 0 oder 1 Vorkommen
  • \ = Escape Zeichen
  • ^ = Anfang der Zeile
  • $ = Ende der Zeile
  • ^ = Negation in einer Zeichenklasse

RegEx sind Objekte

Erzeugung von Regulären Ausdrücken über

  • / /-Literale
  • die Klasse RegExp
/mm\/dd/;             // => /mm\/dd/
new RegExp("mm/dd");  // => /mm\/dd/

let regex = /cat/;
"cats and dogs".match(regex); // => 0
"Cats and Dogs".match(regex); // => null

Reguläre Ausdrücke in JavaScript werden durch die Klasse RegExp repräsentiert auf der man die entsprechenden Methoden zum Umgang mit Regulären Ausdrücken findet. Natürlich kann man einen Regulären Ausdruck auch über den Konstruktor von RegExp erzeugen, z. B. weil man ihn aus einem String dynamisch zusammen bauen möchte.

Ersetzungen in Strings

replace erlaubt Ersetzungen in Strings

  • /.../ ersetzt nur das erste Vorkommen
  • /.../g ersetzt alle Vorkommen
let str = "Mathematik ist toll";
let new_str = str.replace(/Mathematik/, 'Informatik');

log(new_str);  // => "Informatik ist toll"

log("leet".replace(/e/, '3'));  // => "l3et"
log("leet".replace(/e/g, '3')); // => "l33t"

Eine häufige Anwendung von Regulären Ausdrücken ist die Ersetzung von Texten in Strings. Hierzu dienen die sub- und gsub-Methoden von String. Sie bekommen als ersten Parameter einen Regulären Ausdruck und als zweiten die gewünschte Ersetzung. Bei sub wird der erste Match im String ersetzt, bei gsub werden alle ersetzt. Von beiden Methoden gibt es auch eine Bang-Variante (sub! und gsub!), die direkt den String verändern, auf denen sie aufgerufen werden.

Anker

Anker (anchors) erlauben es, das Pattern auf den Anfang oder das Ende eines Strings zu beschränken

  • ^: Anfang der Zeile
  • $: Ende der Zeile
  • \A: Anfang des Strings
  • \Z: Ende des Strings
  • \b: Wortgrenze
  • \B: keine Wortgrenze

Mit Ankern kann man den Regulären Ausdruck auf bestimmte Grenzen einschränken, z. B. auf den Anfang einer Zeile eines Strings etc.

String: "this is\nthe time"

  • /^the/"this is\n[the] time"
  • /is$/"this [is]\nthe time"
  • /\Athis/"[this] is\nthe time"
  • /\Athe/ ⇒ kein Match
  • /\bis/"this [is]\nthe time"
  • /\Bis/"th[is] is\nthe time"

Zeichenklassen

Zeichenklassen (character classes)

  • Zeichen in eckigen Klammern [aeio] matchen jedes einzelne Zeichen im String
  • Sonderzeichen (?, + …) haben keine Bedeutung
  • Bereiche können angegeben werden (z. B. [a-z])
  • Negierung der Zeichen über ^
  • Spezielle Abkürzungen
    • \s: Whitespace
    • \S: Alles außer Whitespace
    • \d: Ziffer
    • \D: Alles außer Ziffern

Zeichenklassen können verwendet werden, um bestimmte Zeichen aus einer Liste von Zeichen zu matchen. Sie werden durch eckige Klammern ([...]) definiert.

String: "Der Preis ist 10 EURO"

  • /[aeiou]/"D[e]r Preis ist 10 EURO."
  • /\s/"Der[ ]Preis ist 10 EURO."
  • /[.]/"Der Preis ist 10 EURO[.]"
  • /[A-Z]/"[D]er Preis ist 10 EURO."
  • /[E-Z]/"Der [P]reis ist 10 EURO."
  • /[0-9]/"Der Preis ist [1]0 EURO."
  • /[0-9][0-9]/"Der Preis ist [10] EURO."
  • /[^Daeiou]/"De[r] Preis ist 10 EURO."

Das Beispiel zeigt die Verwendung von Zeichenklassen in Regulären Ausdrücken und zeigt jeweils den ersten Match.

  • /[aeiou]/ ⇒ matched ein Zeichen und zwar entweder ein a, ein e, ein i ein o oder ein u
  • /\s/ ⇒ matched ein Whitespace, also ein Leerzeichen, ein Tab oder ein Zeilenende
  • /[.]/ ⇒ matched ein Zeichen und zwar den Punkt. Da der Punkt in einer Zeichenklasse (in []) angegeben ist, hat er nicht mehr die ursprüngliche Bedeutung ein beliebiges Zeichen, sondern ist wirklich nur ein ..
  • /[A-Z]/ ⇒ matched alle Großbuchstaben von A bis Z.
  • /[E-Z]/ ⇒ matched alle Großbuchstaben im Bereich E bis Z.
  • /[0-9]/ ⇒ matched alle Ziffern von 0 bis 9
  • /[0-9][0-9]/ ⇒ matched alle zweistelligen Ziffernfolgen von 00 bis 99.
  • /[^Daeiou]/ ⇒ matched alles außer den Zeichen D, a, e, i, o und u.

Wiederholungen

Manchmal weiß man nicht genau, wie oft ein Zeichen oder Muster vorkommt. Dann kann man eine Wiederholung (repetition) nach dem regulären Ausdruck angeben

  • *: 0 oder mehr Vorkommen (Vorsicht!)
  • +: 1 oder mehr Wiederholungen
  • ?: 0 oder 1 Vorkommen (Vorsicht!)
  • {m,n}: Mindestens m, höchstens n Vorkommen
  • {m,}: Mindestens m Vorkommen
  • {,n}: Höchstens n Vorkommen
  • {m}: Genau m Vorkommen

Über Wiederholungen kann man festlegen, wie oft ein Pattern mindestens oder höchstens vorkommen kann. Bei * und ? ist Vorsicht geboten, weil sie auch kein Vorkommen des Musters als Treffer gilt. So matched z. B. /[0-9]?/ jedes Zeichen und nicht wie möglicherweise erwartet nur Zahlen. Ein Muster, dass nur aus einer Zeichenklasse und einem ? oder * besteht ist in den allermeisten Fällen falsch.

"Der Preis ist 10 EURO"

  • /.?[aeiou]{1}/"[De]r Preis ist 10 EURO."
  • /.?[aeiou]{2,3}/"Der P[rei]s ist 10 EURO."
  • /[0-9]+/"Der Preis ist [10] EURO."
  • /[0-9]?/"[]Der Preis ist 10 EURO."

Greedy Match

  • Ein regulärer Ausdruck versucht immer so viele Zeichen wie möglich zu matchen (greedy match)
/<.*>/.exec('<a href="x">Hallo</a>')[0] // => "<a href=\"x\">Hallo</a>"
  • Will man das nicht, muss man an den Regulären Ausdruck ? anhängen, um einen non-greedy match zu bekommen
/<.*?>/.exec('<a href="x">Hallo</a>')[0] // => "<a href=\"x\">"

Ein häufiger Effekt, mit dem Anfängern im Umgang mit regulären Ausdrücken nicht rechnen, ist der Greedy Match (greedy = gierig): Ein Regulärer Ausdruck versucht maximal viele Zeichen zu machten, d. h. wenn er ein Wildcard enthält wird nicht beim ersten Treffer des Teils nach dem Wildcard abgebrochen, sondern es wird weiter gesucht. Wird weiter hinten im String ein Treffer gefunden, so werden maximal viele Zeichen als gematcht betrachtet. Aus diesem Grund, matched /<.*>/ nicht nur den öffnenden HTML-Tag, sondern matched bis zum Ende des schließenden Tags.

Will man, dass ein Regulärer Ausdruck nicht greedy matched, muss man an den Wildcard ein Fragezeichen ? anhängen. So matched /<.*?>/ tatsächlich nur den öffnen Tag und nicht dessen Inhalt.

Gruppierungen

  • Innerhalb eines Regulären Ausdrucks kann man mit () Gruppen bilden, die dann als ein einziger Ausdruck betrachtet werden
  • Auf die geklammerten Ausdrücke kann dann nach dem Match zugegriffen werden
/ab+/.exec('oh ein Rabababer')[0]; // => "ab"
/(ab)+/.exec('oh ein Rabababer')[0]; // => "ababab"

let m = /(2[0-9]{3})-([0-2][0-9])-([0-3][0-9])/.exec('Es ist 2017-09-20');
m[1]; // => 2017
m[2]; // => 09
m[3]; // => 20

Man kann mithilfe von Klammern () im Regulären Ausdruck Gruppen bilden. Diese dienen zwei Zwecken:

  1. Angehängte Wildcards wirken dann auf die gesamte Gruppe und nicht mehr nur auf das letzte Zeichen.
  2. Man kann die Gruppen in Ersetzungsoperationen (siehe unten) oder beim Auslesen der Matches einzeln ansprechen.

Das Beispiel oben zeigt die Funktionsweise der Gruppen: Die Elemente des Datums können über das MatchData-Objekt, das bei der match-Methode zurückgegeben wird einzeln angesprochen werden.

Gruppierungen in Ersetzungen

Backreference

  • Gruppen können auch in Ersetzungen referenziert werden
  • Adressierung über $1, $2, …
log('2022-09-20'.replace(/(2[0-9]{3})-([0-2][0-9])-([0-3][0-9])/, '$3.$2.$1'));

Das Beispiel zeigt eine Ersetzungsoperation mit sub bei der über einen Regulären Ausdruck ein Datum vom ISO-Format (JJJJ-MM-TT) in das deutsche Datumsformat (TT.MM.JJJJ) umgewandelt wird. Die einzelnen Teile des Datums werden über Gruppen in ihre Teile zerlegt und dann über die Backreferences $3, $2, $1 wieder in die neue Reihenfolge gebracht.

Weitere Möglichkeiten

Mit Regulären Ausdrücken kann man noch viel mehr machen

  • Lookahead/Lookbehind: Umgebung beim Match berücksichtigen
  • Schachtelung von Regulären Ausdrücken
  • Namen für Rückreferenzen
  • Bedingte Gruppen
  • Unterroutinen

Reguläre Ausdrücke bieten noch deutlich mehr Möglichkeiten, die hier nicht im Detail beschrieben werden können.


Copyright © 2025 Thomas Smits