Zum Inhalt springen

3. Reguläre Ausdrücke

Zu Zen-Modus wechseln

Viele Aufgaben der Textverarbeitung folgen demselben Schema: Prüfen, ob eine Eingabe einem bestimmten Format entspricht. Alle Stellen finden, an denen ein Format vorkommt. Teile eines Textes nach einem Schema ersetzen. Mit einfachen String-Methoden wie in oder startswith lassen sich solche Aufgaben nur behelfsmäßig lösen: Ob ein Text ein @ enthält, sagt wenig darüber aus, ob er eine gültige E-Mail-Adresse ist.

Das passende Werkzeug sind reguläre Ausdrücke (englisch regular expressions, kurz Regex): eine kompakte Notation, die ein Textmuster beschreibt, etwa “zwei Ziffern, Punkt, zwei Ziffern, Punkt, vier Ziffern” für ein Datum. Reguläre Ausdrücke sind eine eigene kleine Sprache innerhalb der Programmiersprache. Einmal gelernt, funktioniert dieselbe Syntax nahezu überall: in Python, JavaScript und Java ebenso wie in der Suche von VS Code, in Datenbankabfragen und in Kommandozeilenwerkzeugen.

Python stellt reguläre Ausdrücke im Modul re der Standardbibliothek bereit:

import re
text = "Der Kurs beginnt am 15.09.2026 im Raum 214."
match = re.search(r"\d{2}\.\d{2}\.\d{4}", text)
if match:
print(match.group()) # 15.09.2026

re.search() durchsucht den Text nach der ersten Stelle, auf die das Muster passt, und liefert ein Match-Objekt oder None, wenn nichts passt. Das Muster \d{2}\.\d{2}\.\d{4} bedeutet, Baustein für Baustein gelesen: zwei Ziffern, ein Punkt, zwei Ziffern, ein Punkt, vier Ziffern.

Ein regulärer Ausdruck besteht aus gewöhnlichen Zeichen, die für sich selbst stehen, und aus Sonderzeichen mit eigener Bedeutung. Zuerst der Überblick, danach die Gruppen im Einzelnen:

BausteinBedeutungBeispielpasst auf
abcgenau diese ZeichenRaum”Raum”
.ein beliebiges ZeichenH.t”Hut”, “Hit”, “H9t”
[abc]eines der Zeichen a, b, c[aeiou]einen Vokal
[a-z]ein Zeichen aus dem Bereich[0-5]0 bis 5
[^abc]jedes Zeichen außer a, b, c[^0-9]keine Ziffer
\deine Ziffer, wie [0-9]\d\d”42”
\wBuchstabe, Ziffer oder _\w+”word_1”
\sLeerzeichen, Tab, Zeilenumbruch\s” “
*Vorheriges 0-mal oder öfterab*”a”, “ab”, “abbb”
+Vorheriges 1-mal oder öfterab+”ab”, “abbb”
?Vorheriges 0- oder 1-malab?”a”, “ab”
{n}Vorheriges genau n-mal\d{4}”2026”
{n,m}Vorheriges n- bis m-mal\d{1,3}”7”, “42”, “255”
^Anfang des Textes^Der”Der…” am Anfang
$Ende des Textes\.png$”…png” am Ende
(...)Gruppe(ab)+”ab”, “abab”
a|ba oder bpng|jpg”png” oder “jpg”

Eckige Klammern definieren eine Menge erlaubter Zeichen für eine Position. [Jj]a passt auf “Ja” und “ja”. Bereiche werden mit Bindestrich angegeben: [a-zA-Z] ist ein Buchstabe (ohne Umlaute), [0-7] eine Oktalziffer. Ein ^ direkt nach der öffnenden Klammer negiert die Menge: [^"] ist jedes Zeichen außer dem Anführungszeichen.

Für häufig gebrauchte Mengen existieren Abkürzungen: \d (digit) für Ziffern, \w (word) für Buchstaben, Ziffern und Unterstrich, \s (space) für Weißraum. Die Großbuchstaben-Varianten \D, \W, \S bedeuten jeweils das Gegenteil.

Der Punkt . passt auf jedes beliebige Zeichen. Ist ein echter Punkt gemeint, muss er mit Backslash entwertet werden: \.. Das gilt für alle Sonderzeichen; die Suche nach einem Plus, einer Klammer oder einem Fragezeichen schreibt sich \+, \(, \?.

Ein Quantor bezieht sich stets auf den Baustein direkt davor. \d+ bedeutet: eine oder mehrere Ziffern. [a-z]* bedeutet: null oder mehr Kleinbuchstaben. https? bedeutet: “http”, optional gefolgt von einem “s”. Ein häufiges Missverständnis: ab+ heißt “a, dann ein oder mehrere b”, nicht “ab, mehrfach wiederholt”. Soll sich der Quantor auf eine Zeichenfolge beziehen, braucht es eine Gruppe: (ab)+.

Geschweifte Klammern machen die Anzahl exakt: \d{4} sind genau vier Ziffern, \d{2,4} zwei bis vier, \d{2,} mindestens zwei.

Ohne Anker findet re.search() das Muster an beliebiger Stelle: re.search(r"\d{3}", "abc12345") meldet einen Treffer. Anker binden das Muster an eine Position: ^ an den Textanfang, $ an das Textende. Das Muster ^\d{4}$ passt nur, wenn der gesamte Text aus exakt vier Ziffern besteht. Für Wortgrenzen gibt es \b: r"\bRaum\b" findet “Raum”, aber nicht “Raumschiff”.

Für die typische Validierungsaufgabe (“Ist diese Eingabe als Ganzes gültig?”) bietet Python re.fullmatch(), das das Muster automatisch auf den gesamten String bezieht:

def is_postal_code(text):
"""Check for a valid Austrian postal code."""
return re.fullmatch(r"[1-9]\d{3}", text) is not None
print(is_postal_code("4020")) # True
print(is_postal_code("40201")) # False
print(is_postal_code("A-4020")) # False

Österreichische Postleitzahlen sind vierstellig und beginnen nicht mit 0, daher [1-9] an der ersten Position.

Reguläre Ausdrücke entstehen nicht in einem Wurf, sondern iterativ. Als Beispiel dient der Timecode aus Kapitel 1: 01:00:02:11, also HH:MM:SS:FF.

  1. Grobstruktur: vier Zahlenpaare, durch Doppelpunkte getrennt: r"\d{2}:\d{2}:\d{2}:\d{2}". Das passt, allerdings auch auf 99:99:99:99.
  2. Verfeinerung: Minuten und Sekunden laufen von 00 bis 59, die erste Ziffer ist also 0 bis 5: [0-5]\d. Bei 25 fps laufen die Frames von 00 bis 24: [01]\d|2[0-4] (“0 oder 1, dann eine Ziffer; oder 2, dann 0 bis 4”).
  3. Zusammengesetzt und als Ganzes geprüft:
pattern = r"\d{2}:[0-5]\d:[0-5]\d:([01]\d|2[0-4])"
print(re.fullmatch(pattern, "01:00:02:11") is not None) # True
print(re.fullmatch(pattern, "01:61:02:11") is not None) # False
print(re.fullmatch(pattern, "01:00:02:25") is not None) # False

Nach diesem Verfahren entsteht jedes brauchbare Muster: mit einer laxen Version beginnen, Gegenbeispiele suchen, verschärfen. Dazu gehört das passende Werkzeug:

Runde Klammern gruppieren nicht nur, sie fangen auch: Der Teil des Treffers, der auf eine Gruppe entfällt, ist anschließend einzeln abrufbar. Damit werden reguläre Ausdrücke vom Prüfwerkzeug zum Extraktionswerkzeug:

text = "Camera B, clip 0047, recorded on 15.09.2026"
m = re.search(r"(\d{2})\.(\d{2})\.(\d{4})", text)
if m:
print(m.group()) # 15.09.2026 (the whole match)
print(m.group(1)) # 15 (first group: day)
print(m.group(2)) # 09 (second group: month)
print(m.group(3)) # 2026 (third group: year)

Bei mehreren Gruppen wird das Durchzählen fehleranfällig. Benannte Gruppen mit (?P<name>...) machen das Muster länger, den Zugriff aber eindeutig:

pattern = r"(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})"
m = re.search(pattern, text)
print(m.group("year")) # 2026

Ein realistisches Beispiel aus der Medienproduktion: Dateien werden nach dem Schema project_scene-take_camera.extension benannt, etwa commercial_03-2_A.mp4. Ein Muster mit benannten Gruppen zerlegt solche Namen in ihre Bestandteile:

pattern = r"(?P<project>\w+)_(?P<scene>\d{2})-(?P<take>\d+)_(?P<camera>[A-Z])\.(?P<extension>\w+)"
m = re.fullmatch(pattern, "commercial_03-2_A.mp4")
if m:
print(f"scene {m.group('scene')}, take {m.group('take')}, camera {m.group('camera')}")

Der senkrechte Strich | trennt Alternativen: png|jpg|gif passt auf eines der drei Wörter. Ohne Klammern erstreckt sich die Alternative auf das gesamte Muster links und rechts des Strichs; in der Regel grenzt man sie deshalb mit einer Gruppe ein: \.(png|jpg|gif)$ passt auf Dateinamen mit einer dieser drei Endungen.

FunktionZweckliefert
re.search(p, text)erstes Vorkommen an beliebiger StelleMatch oder None
re.fullmatch(p, text)passt der gesamte Text?Match oder None
re.findall(p, text)alle VorkommenListe von Strings/Tupeln
re.finditer(p, text)alle VorkommenIterator von Match-Objekten
re.sub(p, repl, text)Vorkommen ersetzenneuer String
re.split(p, text)Text am Muster zerteilenListe

Daneben existiert re.match(), das nur am Textanfang prüft. Es sorgt regelmäßig für Verwirrung; mit search und fullmatch sind alle Anwendungsfälle klarer abgedeckt.

log = """
2026-09-15 08:03:12 INFO render started: intro.mp4
2026-09-15 08:41:55 ERROR out of memory: scene_04.mp4
2026-09-15 09:12:31 INFO render finished: outro.mp4
2026-09-15 09:13:02 ERROR file not found: music.wav
"""
errors = re.findall(r"ERROR (.+)", log)
print(errors)
# ['out of memory: scene_04.mp4', 'file not found: music.wav']

Enthält das Muster genau eine Gruppe, liefert findall deren Inhalt; bei mehreren Gruppen liefert es Tupel. Die Auswertung von Logdateien gehört zu den häufigsten Regex-Anwendungen in der Praxis und ist Gegenstand von Aufgabe 07.

re.sub() ersetzt jeden Treffer durch einen Ersatztext. Im Ersatztext darf mit \1, \2 oder \g<name> auf die Gruppen des jeweiligen Treffers zugegriffen werden. Die Umstellung eines Datums vom deutschen ins ISO-Format wird damit zu einer Zeile:

text = "Abgabe: 01.10.2026, Präsentation: 15.10.2026"
result = re.sub(r"(\d{2})\.(\d{2})\.(\d{4})", r"\3-\2-\1", text)
print(result)
# Abgabe: 2026-10-01, Präsentation: 2026-10-15

Auch das Suchen-und-Ersetzen in VS Code beherrscht diese Syntax (Regex-Modus über den Knopf .* im Suchfeld aktivieren, im Ersetzen-Feld $1 statt \1). Wer viele Dateien oder Textstellen nach einem Schema umbauen muss, erledigt das damit in Minuten statt Stunden.

Die Grobstruktur einer E-Mail-Adresse (“lokaler Teil, @, Domain mit mindestens einem Punkt”) lässt sich mit den bisherigen Bausteinen formulieren:

pattern = r"[\w.+-]+@[\w-]+(\.[\w-]+)+"
def is_email(text):
return re.fullmatch(pattern, text) is not None
print(is_email("[email protected]")) # True
print(is_email("a@b")) # False
print(is_email("@@@")) # False

Vor dem @ sind Wortzeichen, Punkte, Plus und Minus erlaubt; danach mindestens ein Domain-Teil und mindestens eine Punkt-Endung. Zur fachlichen Einordnung: Eine vollständige E-Mail-Validierung per Regex ist praktisch nicht sinnvoll machbar. Der zugrunde liegende Standard (RFC 5322) erlaubt weit mehr Formen, als je verwendet werden, und ein standardkonformes Muster füllt eine Bildschirmseite. Die übliche Praxis prüft die Grobstruktur wie oben und verifiziert die Adresse anschließend über ein Bestätigungsmail.

Drei Einschränkungen gehören zu einem professionellen Umgang mit regulären Ausdrücken:

  • Einfache Fälle einfach lösen. Ob ein Dateiname auf .png endet, prüft name.endswith(".png") kürzer, schneller und lesbarer als jede Regex. Auch in, startswith, split und strip decken viele Alltagsfälle ab.
  • Verschachtelte Strukturen sind prinzipiell ungeeignet. HTML, XML und JSON erlauben beliebig tiefe Verschachtelung. Reguläre Ausdrücke können solche Strukturen aus theoretischen Gründen nicht korrekt erfassen; dafür existieren Parser. Für JSON stellt die Standardbibliothek das json-Modul bereit (Kapitel 9).
  • Lesbarkeit hat Vorrang. Ein Muster, das eine Woche später niemand mehr entziffern kann, ist ein Wartungsproblem. Abhilfe schaffen benannte Gruppen, das Zerlegen langer Muster in benannte Teilstrings und ein Kommentar, der den fachlichen Zweck samt zwei Beispielen dokumentiert.

Lernergebnisse: Was Sie nach diesem Kapitel können sollten

Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“

Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:

  • Nennen: die Bausteine regulärer Ausdrücke (Zeichenklassen, Quantoren, Anker, Gruppen, Alternativen) und ihre Bedeutung nennen.
  • Anwenden: Muster für typische Aufgaben (Postleitzahl, Datum, Timecode, E-Mail, Log-Zeilen, Dateinamen) formulieren und mit einem Werkzeug wie regex101 testen.
  • Anwenden: Texte mit search, fullmatch, findall und sub durchsuchen, validieren und umformen.
  • Analysieren: Teilinformationen mit Capture-Gruppen (auch benannten) aus Texten extrahieren.
  • Beurteilen: entscheiden, wann ein regulärer Ausdruck das passende Werkzeug ist und wann String-Methoden oder ein Parser vorzuziehen sind.
  • Aufgabe 06 - Musterjagd
  • Aufgabe 07 - Logdatei-Detektiv