Einführung
Reguläre Ausdrücke — auch bekannt als Regex oder Regexp — sind Zeichenfolgen, die ein Suchmuster definieren. Sie gehören zu den mächtigsten Werkzeugen im Arsenal eines Entwicklers und ermöglichen es, Text mit einem einzigen kompakten Ausdruck zu durchsuchen, zu validieren, zu extrahieren und zu transformieren.
Ob Sie eine E-Mail-Adresse in einem Web-Formular validieren, Daten aus Logs extrahieren oder in tausenden Dateien eine komplexe Suchen-und-Ersetzen-Operation durchführen — reguläre Ausdrücke erlauben es Ihnen, genau das auszudrücken, was Sie suchen, in einer prägnanten, portablen Notation, die nahezu jede Programmiersprache und die meisten Texteditoren verstehen.
Kurze Geschichte der Regulären Ausdrücke
Die Geschichte der regulären Ausdrücke reicht bis in die Grundlagen der theoretischen Informatik zurück:
- 1951 — Der Mathematiker Stephen Kleene formalisiert das Konzept der regulären Sprachen und führt den Kleene-Stern (
*) als Teil seiner Arbeit zur Automatentheorie ein. - 1968 — Ken Thompson implementiert reguläre Ausdrücke im QED-Texteditor und anschließend in Unix-Werkzeugen wie
grep,sedundawk. - 1986 — POSIX standardisiert zwei Varianten: BRE (Basic Regular Expressions) und ERE (Extended Regular Expressions).
- 1997 — Philip Hazel erstellt die PCRE-Bibliothek (Perl Compatible Regular Expressions), die zum De-facto-Standard für leistungsfähige Regex-Funktionen wird.
- 1999 — ECMAScript 3 standardisiert das
RegExp-Objekt von JavaScript. - 2015 — ES6 fügt die Flags
u(Unicode) undy(sticky) hinzu. - 2018 — ES2018 fügt benannte Capture-Gruppen (
(?<name>...)) und Lookbehind-Assertionen hinzu.
POSIX vs PCRE vs JavaScript RegExp
| Funktion | BRE/ERE (POSIX) | PCRE | JavaScript RegExp |
|---|---|---|---|
| Lookahead | ✗ | ✓ | ✓ |
| Lookbehind | ✗ | ✓ | ✓ (ES2018+) |
| Benannte Gruppen | ✗ | ✓ | ✓ (ES2018+) |
| Nicht-gieriges Matching | ✗ | ✓ | ✓ |
| Unicode | Eingeschränkt | ✓ | ✓ (mit Flag u) |
| Rückreferenzen | ✓ | ✓ | ✓ |
Kern-Syntax-Referenz
Schnellreferenz-Tabelle
| Muster | Bedeutung |
|---|---|
. |
Beliebiges Zeichen außer Zeilenumbruch |
^ |
Anfang der Zeichenkette / Zeilenanfang (mit Flag m) |
$ |
Ende der Zeichenkette / Zeilenende (mit Flag m) |
\d |
Beliebige Ziffer [0-9] |
\D |
Beliebiges Nicht-Ziffer-Zeichen |
\w |
Wortzeichen [a-zA-Z0-9_] |
\W |
Nicht-Wortzeichen |
\s |
Leerzeichen (Leerzeichen, Tabulator, Zeilenumbruch…) |
\S |
Nicht-Leerzeichen |
[abc] |
Zeichenklasse — stimmt mit a, b oder c überein |
[^abc] |
Negierte Klasse — stimmt mit allem außer a, b, c überein |
[a-z] |
Zeichenbereich |
* |
0 oder mehr Male (gierig) |
+ |
1 oder mehr Male (gierig) |
? |
0 oder 1 Mal (gierig) |
{n} |
Genau n Male |
{n,m} |
Zwischen n und m Male (gierig) |
*? +? ?? |
Faule (nicht-gierige) Entsprechungen |
(abc) |
Capture-Gruppe |
(?:abc) |
Nicht-Capture-Gruppe |
(?<name>abc) |
Benannte Capture-Gruppe |
| |
Alternation — stimmt mit links ODER rechts überein |
(?=...) |
Positives Lookahead |
(?!...) |
Negatives Lookahead |
(?<=...) |
Positives Lookbehind |
(?<!...) |
Negatives Lookbehind |
Zeichenklassen
Zeichenklassen ermöglichen es, ein Zeichen aus einer Menge abzugleichen. [aeiou] stimmt mit einem beliebigen Vokal überein. [a-zA-Z] stimmt mit einem beliebigen Buchstaben überein. [^0-9] stimmt mit einem beliebigen Zeichen überein, das keine Ziffer ist.
Häufig verwendete Kurzschreibweisen:
\dentspricht[0-9]\wentspricht[a-zA-Z0-9_]\sstimmt mit Leerzeichen, Tabulator (\t), Zeilenumbruch (\n), Wagenrücklauf (\r) und anderen Leerzeichen überein
Quantifikatoren: Gierig vs Faul
Standardmäßig sind Quantifikatoren gierig — sie stimmen mit so viel wie möglich überein. Betrachten Sie den HTML-String <b>fett</b> und <i>kursiv</i>:
<.*> → stimmt mit der gesamten Zeichenkette von <b> bis </i> überein (gierig)
<.*?> → stimmt nacheinander mit <b>, </b>, <i>, </i> überein (faul)
Das Hinzufügen von ? nach einem Quantifikator macht ihn faul (nicht-gierig): Er stimmt mit so wenig wie möglich überein, solange das Gesamtmuster noch erfolgreich ist.
Anker
^stimmt mit dem Anfang der Zeichenkette überein (oder dem Zeilenanfang mit Flagm).$stimmt mit dem Ende der Zeichenkette überein (oder dem Zeilenende mitm).\bstimmt mit einer Wortgrenze überein — dem Übergang zwischen einem Wortzeichen und einem Nicht-Wortzeichen.\Bstimmt mit einer Nicht-Wortgrenze überein.
Gruppen und Rückreferenzen
Capture-Gruppen (...) erfassen den übereinstimmenden Text, auf den Sie später als \1, \2 usw. (Rückreferenzen) verweisen oder über das Match-Ergebnis-Array zugreifen können.
Nicht-Capture-Gruppen (?:...) gruppieren Teilmuster ohne eine Capture zu erstellen, was effizienter ist, wenn Sie den erfassten Wert nicht benötigen.
Benannte Gruppen (?<jahr>\d{4}) ermöglichen das Referenzieren von Captures per Name (match.groups.jahr in JavaScript), was Muster viel lesbarer macht.
Lookahead und Lookbehind
Diese nullbreiten Assertionen stimmen mit einer Position überein, nicht mit einem Zeichen:
\d+(?= Euro) → stimmt mit Ziffern nur überein, wenn " Euro" folgt
\d+(?! Euro) → stimmt mit Ziffern überein, wenn " Euro" NICHT folgt
(?<=\$)\d+ → stimmt mit Ziffern nur überein, wenn "$" vorangeht
(?<!\$)\d+ → stimmt mit Ziffern überein, wenn "$" NICHT vorangeht
Lookaheads und Lookbehinds konsumieren keine Zeichen, sodass der übereinstimmende Text den Lookahead/Lookbehind-Teil nicht enthält.
Flags
| Flag | Name | Wirkung |
|---|---|---|
i |
Groß-/Kleinschreibung ignorieren | [a-z] stimmt auch mit [A-Z] überein |
g |
Global | Alle Übereinstimmungen finden, nicht nur die erste |
m |
Mehrzeilig | ^ und $ stimmen mit Zeilengrenzen überein |
s |
dotAll | . stimmt auch mit Zeilenumbrüchen überein |
u |
Unicode | Aktiviert vollständiges Unicode-Matching; erforderlich für \p{} |
y |
Sticky | Stimmt nur ab Position lastIndex überein |
x |
Ausführlich/Erweitert | Erlaubt Leerzeichen und Kommentare (nur PCRE/Python) |
Das x-Flag (ausführlich) ist besonders wertvoll für die Dokumentation komplexer Muster:
import re
pattern = re.compile(r"""
^ # Anfang der Zeichenkette
(?P<year>\d{4}) # 4-stelliges Jahr
-
(?P<month>0[1-9]|1[0-2]) # Monat 01–12
-
(?P<day>0[1-9]|[12]\d|3[01]) # Tag 01–31
$
""", re.VERBOSE)
Häufige Muster mit Realen Beispielen
E-Mail-Validierung
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
Aufschlüsselung:
^[a-zA-Z0-9._%+-]+— lokaler Teil (Buchstaben, Ziffern und ausgewählte Sonderzeichen)@— literales @-Zeichen[a-zA-Z0-9.-]+— Domainname\.[a-zA-Z]{2,}$— TLD mit 2+ Buchstaben
URL-Matching
https?://(?:www\.)?[a-zA-Z0-9-]+(?:\.[a-zA-Z]{2,})+(?:/[^\s]*)?
ISO-Datum (YYYY-MM-DD)
\b\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])\b
Validiert Monat 01–12 und Tag 01–31. Beachten Sie: monatsabhängige Tagesbereiche werden nicht validiert (z.B. würde der 30. Februar die Validierung bestehen).
IPv4-Adresse
\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b
Aufschlüsselung:
25[0-5]— 250–2552[0-4]\d— 200–249[01]?\d\d?— 0–199
US-Telefonnummer
\+?1?\s?[\(]?\d{3}[\)]?[-.\s]?\d{3}[-.\s]?\d{4}
Stimmt mit Formaten wie (555) 123-4567, 555-123-4567, +1 555 123 4567 überein.
Hexadezimaler Farbcode
#(?:[0-9A-Fa-f]{3}){1,2}\b
Stimmt sowohl mit 3-stelligen (#F00) als auch 6-stelligen (#FF0000) Hex-Farben überein.
Reguläre Ausdrücke in Verschiedenen Programmiersprachen
JavaScript
// Literal-Syntax mit Flags
const regex = /^hello\s+world$/im;
const match = "Hello World".match(regex);
// Konstruktor-Syntax (nützlich für dynamische Muster)
const term = "world";
const dynamic = new RegExp(`hello\\s+${term}`, "im");
// Alle Vorkommen ersetzen
const result = "foo bar foo".replaceAll(/foo/g, "baz");
// Benannte Captures (ES2018+)
const dateRegex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const { year, month, day } = "2024-03-15".match(dateRegex).groups;
Python
import re
# Für Wiederverwendung kompilieren
pattern = re.compile(r'^hello\s+world$', re.IGNORECASE | re.MULTILINE)
match = pattern.match("Hello World")
# Alle Übereinstimmungen finden
dates = re.findall(r'\d{4}-\d{2}-\d{2}', text)
# Mit einer Funktion ersetzen
result = re.sub(r'\b\d+\b', lambda m: str(int(m.group()) * 2), "1 plus 2 ist 3")
# Benannte Gruppen
m = re.search(r'(?P<year>\d{4})-(?P<month>\d{2})', "2024-03")
print(m.group('year')) # 2024
Java
import java.util.regex.*;
Pattern p = Pattern.compile("^hello\\s+world$",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Hello World");
boolean found = m.matches();
// Gruppen extrahieren
Pattern datePattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher dm = datePattern.matcher("Heute ist 2024-03-15");
if (dm.find()) {
String year = dm.group(1);
}
Go
import "regexp"
re := regexp.MustCompile(`(?im)^hello\s+world$`)
match := re.FindString("Hello World")
// Alle Teilübereinstimmungen finden
dateRe := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
all := dateRe.FindAllStringSubmatch(text, -1)
for _, m := range all {
year, month, day := m[1], m[2], m[3]
_ = year; _ = month; _ = day
}
// Benannte Gruppen
namedRe := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})`)
match2 := namedRe.FindStringSubmatch("2024-03")
yearIdx := namedRe.SubexpIndex("year")
fmt.Println(match2[yearIdx]) // 2024
Performance und Katastrophales Backtracking
Wie Backtracking Funktioniert
Die meisten Regex-Engines verwenden NFA-basiertes (nichtdeterministischer endlicher Automat) Matching, was bedeutet, dass sie mehrere Pfade durch das Muster versuchen können, wenn ein früher Versuch scheitert. Dieses Backtracking ermöglicht Funktionen wie Lookaheads und Rückreferenzen — kann aber auch eine Performance-Falle sein.
Der Katastrophale Fall
Betrachten Sie das Muster (a+)+ angewandt auf den String "aaaaaX":
- Das äußere
+versucht, so viele Gruppen wie möglich zu matchen. - Wenn die Engine
Xerreicht und scheitert, macht sie Backtracking und versucht verschiedene Wege, diea-Zeichen auf die Wiederholungen der Gruppe aufzuteilen. - Für einen String der Länge n gibt es 2^(n-1) mögliche Aufteilungen — was zu exponentieller Zeitkomplexität führt.
(a+)+ auf "aaaaaaaaaaaaaaaaaX" → kann Sekunden oder Minuten dauern!
Andere gefährliche Muster umfassen (a|aa)+, (\w+\s*)+ und alles mit verschachtelten Quantifikatoren über überlappenden Zeichenklassen.
Wie Man Es Vermeidet
- Verschachtelte Quantifikatoren über denselben Zeichensatz vermeiden:
(a+)+→ stattdessena+verwenden. - Atomare Gruppen
(?>...)oder possessive Quantifikatorena++(PCRE) verwenden, um Backtracking in bereits gematchte Gruppen zu verhindern. - Spezifischer sein:
.*durch eine Zeichenklasse ersetzen, die Begrenzer ausschließt (z.B.[^"]*innerhalb von Zeichenketten in Anführungszeichen). - Muster wenn möglich verankern, damit die Engine schnell scheitern kann.
- Timeout in Produktionscode verwenden, wenn nicht vertrauenswürdige Eingaben verarbeitet werden.
Wie Man Einen Komplexen Regulären Ausdruck Liest
Zerlegen wir ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$:
^ → Anker: Anfang der Zeichenkette
[a-zA-Z0-9._%+-]+ → ein oder mehr erlaubte Zeichen im lokalen Teil
@ → literales @
[a-zA-Z0-9.-]+ → ein oder mehr Domain-Zeichen
\. → literaler Punkt (escaped)
[a-zA-Z]{2,} → TLD: 2 oder mehr Buchstaben
$ → Anker: Ende der Zeichenkette
Tipp: Verwenden Sie den Regex-Tester dieser Website, um jede Capture-Gruppe hervorzuheben und genau zu sehen, welcher Teil der Eingabe mit jedem Token übereinstimmt. Bauen Sie Ihr Muster schrittweise auf — beginnen Sie mit dem einfachsten gültigen Teil, verifizieren Sie ihn, dann erweitern Sie ihn.
Bewährte Praktiken
- Einmal kompilieren, oft verwenden. Das Vorcompilieren eines Musters (z.B.
re.compile()in Python,Pattern.compile()in Java) ist viel effizienter als es bei jedem Aufruf neu zu parsen. - Nicht-Capture-Gruppen
(?:...)bevorzugen, wenn der erfasste Wert nicht benötigt wird. Es signalisiert die Absicht und vermeidet unnötige Speicherzuweisung. - Raw Strings für Muster verwenden. In Python
r'\d+'statt'\\d+'verwenden, um doppeltes Escaping zu vermeiden. In JavaScript verarbeitet die Literal-Syntax/\d+/dies automatisch. - Captures benennen.
(?<jahr>\d{4})ist viel wartungsfreundlicher als die Verwendung des Gruppenindex\1. - Mit Grenzfällen testen: leere Zeichenketten, Zeichenketten die fast-aber-nicht-ganz übereinstimmen, Unicode-Zeichen und sehr lange Eingaben.
- Komplexe Muster dokumentieren mit dem
x-Flag (ausführlich) in Python/PCRE oder mit Inline-Kommentaren im Code. - Niemals Regex zum vollständigen HTML- oder XML-Parsing verwenden. Stattdessen eine geeignete Parser-Bibliothek einsetzen.
- Eingaben serverseitig validieren. Clientseitige Regex-Validierung verbessert die UX, darf aber nicht die einzige Verteidigungslinie sein.
Häufig Gestellte Fragen (FAQ)
F: Was ist der Unterschied zwischen match() und search() in Python?
A: re.match() stimmt nur am Anfang der Zeichenkette überein. re.search() durchsucht die gesamte Zeichenkette nach einer Übereinstimmung. Verwenden Sie re.fullmatch(), um zu verlangen, dass das Muster mit der gesamten Zeichenkette übereinstimmt.
F: Warum bedeutet ^ innerhalb einer Zeichenklasse [^abc] etwas anderes?
A: Innerhalb einer Zeichenklasse negiert ^ als erstes Zeichen die Klasse — es stimmt mit jedem Zeichen überein, das nicht in der Menge ist. Außerhalb einer Zeichenklasse ist ^ ein Anker für den Anfang der Zeichenkette.
F: Kann ich Regex zum Parsen von HTML verwenden?
A: Für einfache, klar definierte Extraktionen aus bekannten HTML-Strukturen kann Regex funktionieren. Aber HTML ist keine reguläre Sprache — sie erlaubt beliebige Verschachtelung und optionale schließende Tags. Verwenden Sie einen geeigneten HTML-Parser (z.B. BeautifulSoup in Python, DOMParser in JS) für robustes Parsing.
F: Was ist der Unterschied zwischen gierigen und possessiven Quantifikatoren?
A: Gierige Quantifikatoren machen Backtracking — sie versuchen das maximale Match und geben Zeichen zurück, wenn nötig. Possessive Quantifikatoren (z.B. a++ in PCRE) geben nie zurück — einmal gematcht, ist das Match gesperrt. Dies verhindert katastrophales Backtracking, kann aber auch dazu führen, dass ein Match scheitert, wo ein gieriger Quantifikator erfolgreich gewesen wäre.
F: Wie stimme ich mit einem literalen Punkt . oder einer Klammer ( überein?
A: Escapen Sie sie mit einem Backslash: \. stimmt mit einem literalen Punkt überein, \( mit einer literalen öffnenden Klammer.
F: Ist Regex standardmäßig case-sensitiv?
A: Ja. Verwenden Sie das i-Flag (/muster/i in JavaScript, re.IGNORECASE in Python), um case-insensitives Matching zu aktivieren.
F: Was matcht \b?
A: \b ist eine nullbreite Wortgrenz-Assertion. Es stimmt mit der Position zwischen einem Wortzeichen (\w) und einem Nicht-Wortzeichen (\W) überein.
F: Wie teste ich, ob eine gesamte Zeichenkette mit einem Muster übereinstimmt?
A: Verankern Sie mit ^ und $: ^muster$. In Python können Sie auch re.fullmatch() verwenden. In JavaScript verwenden Sie .test() mit Ankern oder prüfen Sie, ob match()[0].length === input.length.
Verwenden Sie den Regex-Tester dieser Website, um mit jedem Muster in diesem Leitfaden zu experimentieren. Fügen Sie ein beliebiges Muster ein, geben Sie Ihren Teststring ein und sehen Sie Übereinstimmungen in Echtzeit hervorgehoben.