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.

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

  • Alle Zeichen, bis auf . | ( ) [ ] { } + \ ^ $ * ? matchen sich selbst
  • . steht für jedes beliebige Zeichen
  • Können mit Pattern.matches auf einen String angewandt werden
    • true, wenn es einen Match gibt
    • false, wenn die RegEx nicht matched
Pattern.matches("cat.*", "cat and dog"); // -> true
Pattern.matches("cat.*", "catch"); // -> true
Pattern.matches("cat.*", "Cat"); // -> false

Da Reguläre Ausdrücke kein fester Bestandteil der Sprache sind, werden Sie in Java durch Strings repräsentiert, die man mithilfe der Klasse Pattern in ein entsprechendes Objekt umwandeln kann.

Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("dogs and cats");
if (m.find()) {
    System.out.println(m.start()); // -> 9
    System.out.println(m.end()); // -> 12
}

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), kann als Ergebnis die Position ausgelesen werden. Will man weitere Treffer im String finden, muss man find() wiederholt aufrufen.

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

Ersetzungen in Strings

replaceFirst und replaceAll erlauben Ersetzungen in Strings

  • replaceFirst ersetzt nur das erste Vorkommen
  • replaceAll ersetzt alle Vorkommen
String str = "Mathematik ist toll";
String newStr = str.replaceFirst("Mathematik", "Informatik");

System.out.println(newStr); // -> "Informatik ist toll"

System.out.println("leet".replaceFirst("e", "3")); // -> "l3et"
System.out.println("leet".replaceAll("e", "3")); // -> "l33t"

Eine häufige Anwendung von Regulären Ausdrücken ist die Ersetzung von Texten in Strings. Hierzu dienen die replaceFirst- und replaceAll-Methoden von String. Sie bekommen als ersten Parameter einen Regulären Ausdruck und als zweiten die gewünschte Ersetzung. Bei replaceFirst wird der erste Match im String ersetzt, bei replaceAll werden alle ersetzt.

Matcher-Objekt

Matcher-Objekt

  • matcher-Methode von Pattern liefert Objekt zurück
  • erlaubt den gematchten Teil zu extrahieren
String s = "cats and dogs";
Matcher m = Pattern.compile("at").matcher(s);
m.find();
System.out.printf("%s[%s]%s",
    s.substring(0, m.start()),
    s.substring(m.start(), m.end()),
    s.substring(m.end()));
Ausgabe
c[at]s and dogs

Methode matcher ein Objekt vom Typ Matcher zurück, das eine Reihe von nützlichen Methoden hat, um auf die Ergebnisse zuzugreifen.

  • start(): Start-Index des Treffers
  • end(): End-Index des Treffers
  • group(n): Zugriff auf den nten Match (bei Gruppen)

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
  • 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)
<.*> mit <a href="x">Hallo</a> => <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
<.*?> mit <a href="x">Hallo</a> => <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 weitergesucht. 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
String s = "oh ein Rabababer";
Matcher m = Pattern.compile("(ab)+").matcher(s);
m.find();
System.out.printf(m.group()); //  -> "ababab"

s = "Es ist 2022-09-20";
m = Pattern.compile("(2[0-9]{3})-([0-2][0-9])-([0-3][0-9])").matcher(s);
m.find();
System.out.printf(m.group(1)); //  -> 2022
System.out.printf(m.group(2)); //  -> 09
System.out.printf(m.group(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 Matcher-Objekt, das bei der matcher-Methode zurückgegeben wird einzeln angesprochen werden.

Gruppierungen in Ersetzungen

Backreference

  • Gruppen können auch in Ersetzungen referenziert werden
  • Adressierung über $1, $2, …
String s = "Es ist 2022-09-20.";
String p = "(2[0-9]{3})-([0-2][0-9])-([0-3][0-9])";
s.replaceAll(p, "$3.$2.$1");
Ausgabe
Es ist 20.09.2022.

Das Beispiel zeigt eine Ersetzungsoperation mit replaceAll 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