Skip to content
Zurück zum Blog
Tutorials

Regex-Spickzettel: Metazeichen, Gruppen und Lookarounds

Regex-Spickzettel: Metazeichen, Quantoren, Anker, Gruppen, Lookarounds und über 15 JavaScript- und Python-Muster, plus konkrete Fixes gegen katastrophales Backtracking.

12 Min. Lesezeit

Regex-Spickzettel: Metazeichen, Gruppen und Lookarounds (komplette Referenz)

Ein regulärer Ausdruck ist eine kleine Mustersprache zum Abgleich von Text. \d+ bedeutet „eine oder mehrere Ziffern”, ^Error bedeutet „eine Zeile, die mit Error beginnt”. Dieser Spickzettel sammelt die komplette Syntax auf einer Seite, dazu über 15 Muster, die Sie direkt in JavaScript oder Python einsetzen können.

Geschrieben für Entwickler, die schon wissen, was eine Zeichenkette ist, und eine Referenz statt einer Rundreise brauchen. Wenn Sie nur die Symbole nachschlagen wollen, springen Sie zur Schnellreferenz-Tabelle. Wer schon einmal eine Regex einen Server hat lahmlegen sehen, sollte die Abschnitte zu Lookarounds und Fallstricken lesen.

1. Was ist Regex und warum brauchen Sie sie 2026 immer noch

Eine Regex ist ein Muster, das in eine Zustandsmaschine kompiliert wird, eine Zeichenkette scannt und entweder passt oder nicht passt. Die Grammatik ist überschaubar, der Einsatzbereich nicht.

KI kann Ihnen ein Muster entwerfen, aber in drei Situationen schreiben Sie es weiterhin selbst:

  • Log-Parsing: Sie haben zehn Millionen Zeilen nginx-Zugriffslogs und brauchen jede 5xx-Anfrage eines bestimmten User-Agents. Eine 40 Zeichen lange Regex über grep -E läuft in Sekunden; ein LLM-Aufruf pro Zeile nicht.
  • Formular- und Feldvalidierung: Telefonnummern, Postleitzahlen, ISO-Zeitstempel, Lizenzschlüssel. Das Muster sitzt direkt neben dem Eingabefeld und läuft im Browser bei jedem Tastendruck.
  • Massenhaftes Suchen und Ersetzen: Refactoring über tausend Dateien, bei dem Sie einen Namen erfassen und neu einfügen müssen. sed, ripgrep und das „In Dateien ersetzen” Ihres Editors sprechen Regex von Haus aus.

Für die JSON-Hälfte desselben Werkzeugkastens schauen Sie in unseren jq-Spickzettel für die Kommandozeile.

1.1 Wie man ein Regex-Muster liest (5-Sekunden-Tutorial für reguläre Ausdrücke)

Die meisten Muster lassen sich am leichtesten von links nach rechts lesen, Token für Token. Nehmen wir ^[A-Z]\w+\d{2,4}$ als Beispiel:

  • ^ verankert den Treffer am Anfang der Zeichenkette.
  • [A-Z] trifft genau einen Großbuchstaben.
  • \w+ trifft ein oder mehrere Wortzeichen (Buchstaben, Ziffern, Unterstrich).
  • \d{2,4} trifft zwei bis vier Ziffern.
  • $ verankert am Ende der Zeichenkette.

Das gesamte Muster trifft also Order42, Job1999. Der Trick ist, zuerst die Anker zu lesen, dann die Zeichenklassen, dann die Quantoren und zuletzt die Grenzen.

2. Schnellreferenz-Tabelle

Wegen dieses Abschnitts kommen die meisten Leser her. Kopieren Sie, was Sie brauchen.

Metazeichen

MusterTrifft auf
.Jedes Zeichen außer Zeilenumbruch (oder jedes Zeichen mit dem s/dotall-Flag)
\dEine Ziffer ([0-9], oder alle Unicode-Ziffern mit dem u-Flag)
\DEine Nicht-Ziffer
\wEin Wortzeichen ([A-Za-z0-9_])
\WEin Nicht-Wortzeichen
\sBeliebiger Whitespace (Leerzeichen, Tab, Zeilenumbruch, …)
\SBeliebiger Nicht-Whitespace

Quantoren

MusterTrifft auf
*0 oder mehr (gierig)
+1 oder mehr (gierig)
?0 oder 1 (gierig)
{n}Genau n-mal
{n,m}Zwischen n und m-mal
{n,}n-mal oder häufiger
*?, +?, ??, {n,m}?Faule (lazy) Varianten jedes Quantors

Anker

MusterTrifft auf
^Anfang der Zeichenkette (oder Zeilenanfang mit m-Flag)
$Ende der Zeichenkette (oder Zeilenende mit m-Flag)
\bWortgrenze
\BKeine Wortgrenze
\AAbsoluter Anfang der Zeichenkette (Python)
\ZAbsolutes Ende der Zeichenkette (Python)

Zeichenklassen

MusterTrifft auf
[abc]Eines von a, b, c
[^abc]Alles außer a, b, c
[a-z]Jeder Kleinbuchstabe
[0-9]Jede Ziffer
\p{L}Jeder Unicode-Buchstabe (u-Flag in JS, Standard in Python re)

Gruppen

MusterTrifft auf
(...)Fanggruppe (Capture Group)
(?:...)Nicht-fangende Gruppe
(?<name>...)Benannte Capture (JS ES2018+); Python nutzt (?P<name>...)
\1, \2Rückverweis auf Gruppe 1, 2

Lookaround

MusterTrifft auf
(?=...)Positiver Lookahead
(?!...)Negativer Lookahead
(?<=...)Positiver Lookbehind
(?<!...)Negativer Lookbehind

Flags

FlagWirkung
iGroß-/Kleinschreibung wird ignoriert
mMultiline: ^ und $ greifen pro Zeile
sDotall: . trifft auch Zeilenumbrüche
gGlobal (JS): alle Treffer finden
uUnicode-Modus
ySticky (JS): verankert an lastIndex

3. Metazeichen und Zeichenklassen

3.1 Literale gegen Sonderzeichen

Die meisten Zeichen sind Literale. Die 12 Metazeichen, die Sie maskieren müssen, wenn Sie sie als sich selbst gemeint haben, sind:

. ^ $ * + ? ( ) [ ] { } | \

. zu vergessen ist mit Abstand der häufigste Regex-Bug. \. trifft einen literalen Punkt. Innerhalb einer Zeichenklasse trifft [.] ebenfalls einen literalen Punkt: die meisten Metazeichen verlieren ihre Sonderbedeutung innerhalb von [...], mit Ausnahme von ], \, ^ (am Anfang) und - (in der Mitte).

3.2 Zeichenklassen-Kurzformen

Die Kurzklassen wirken einfach, bis Unicode ins Spiel kommt:

// JavaScript: ohne das u-Flag ist \d nur ASCII
/\d/.test('5');    // true
/\d/.test('٥');    // false (arabisch-indische Ziffer)
/\d/u.test('٥');   // false — selbst mit u bleibt \d in JS auf ASCII
/\p{N}/u.test('٥'); // true — \p{N} ist die Unicode-fähige Ziffernklasse
# Python: das re-Modul behandelt \d standardmäßig als Unicode
import re
re.match(r'\d', '٥')  # <Match span=(0, 1)>
re.match(r'(?a)\d', '٥')  # None — (?a) erzwingt ASCII

Solange Sie nur englisches ASCII verarbeiten, sind \d und [0-9] austauschbar. Sobald jemand einen Namen mit Akzent einfügt, brauchen Sie \p{L} statt \w.

3.3 Eigene Zeichenklassen

// JavaScript
/[A-Za-z][A-Za-z0-9_-]{2,29}/.test('valid_handle-1'); // true

// Negation und Bereiche kombiniert
/[^aeiou\s]/g  // jedes Zeichen, das weder Vokal noch Whitespace ist

Für Unicode-Kategorien steht \p{L} für „jeder Buchstabe”, \p{N} für „jede Zahl”, \p{Script=Han} für „jedes Han-Zeichen”. JavaScript verlangt das u-Flag; Python unterstützt \p{...} nur über das PyPI-Paket regex, nicht über die Standardbibliothek re.

Wer auf der Kommandozeile arbeitet, stößt zudem auf POSIX-Zeichenklassen:

POSIX-KlasseTrifft aufASCII-Äquivalent
[[:alpha:]]Buchstaben[A-Za-z]
[[:digit:]]Ziffern[0-9] (\d)
[[:alnum:]]Buchstaben + Ziffern[A-Za-z0-9]
[[:space:]]Whitespace\s
[[:upper:]]Großbuchstaben[A-Z]
[[:lower:]]Kleinbuchstaben[a-z]

POSIX-Klassen funktionieren in grep -E, sed -E. In JavaScript oder Pythons re greifen sie nicht — nutzen Sie dort \d, \s, \w.

4. Quantoren und gierig gegen faul

4.1 Grundquantoren

/a*/.exec('aaab')      // ['aaa']     — 0 oder mehr
/a+/.exec('aaab')      // ['aaa']     — 1 oder mehr
/a?/.exec('aaab')      // ['a']       — 0 oder 1
/a{2,3}/.exec('aaaab') // ['aaa']     — 2 bis 3

4.2 Gierig gegen faul

Quantoren sind standardmäßig gierig: Sie schnappen sich so viel wie möglich und geben dann Stück für Stück nach, damit das gesamte Muster passt. Hängen Sie ein ? an, um sie auf faul umzustellen.

const html = '<p>one</p><p>two</p>';

html.match(/<p>.*<\/p>/)[0];   // '<p>one</p><p>two</p>'   (gierig frisst beides)
html.match(/<p>.*?<\/p>/)[0];  // '<p>one</p>'             (faul stoppt beim ersten)

Beim Extrahieren von Tags oder gequoteten Zeichenketten greifen Sie meist zur faulen Variante. Schneller ist allerdings, ganz auf . zu verzichten und eine negierte Klasse zu nutzen: <p>[^<]*</p> schlägt <p>.*?</p>, weil die Engine nirgendwo zurückspringen kann.

4.3 Katastrophales Backtracking

So legt eine Regex Ihren Server lahm. Wenn Sie einen Quantor in einem anderen Quantor mit mehrdeutiger Überlappung verschachteln, erkundet die Engine exponentiell viele Pfade, bevor sie aufgibt.

// Tun Sie das nicht
/(a+)+b/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'); // dauert Sekunden

Bei 41 as gefolgt von ! probiert die Engine grob 2^41 Aufteilungen aus, bevor sie merkt, dass das b fehlt. Es gibt drei Auswege:

  1. Flachen Sie das Muster ab: /a+b/ erledigt dasselbe ganz ohne Verschachtelung.
  2. Atomare Gruppe nutzen (Python regex, PCRE, Java, Ruby): (?>a+)+b. Sobald a+ gepasst hat, weigert sich die Engine, beim Backtracking dort wieder hineinzulaufen.
  3. Engine wechseln: Gos regexp, RE2 und Rusts regex-Crate nutzen eine NFA mit linearer Laufzeit und können konstruktionsbedingt nicht katastrophal backtracken.

JavaScript und Pythons re backtracken beide und haben in der Standardbibliothek keine atomaren Gruppen (das PyPI-Paket regex rüstet sie für Python nach). Solange Sie die Eingabelänge kontrollieren, ist das in Ordnung; sobald die Eingabe vom Nutzer kommt, prüfen Sie zuerst die Länge oder kompilieren gegen RE2.

5. Anker und Wortgrenzen

5.1 ^ und $

Standardmäßig ist ^ der Anfang der gesamten Eingabe und $ deren Ende. Mit dem m-Flag (Multiline) werden daraus Zeilenanfang und Zeilenende:

const log = 'INFO start\nERROR boom\nINFO done';
log.match(/^ERROR.*/);    // null  (Single-Line-Modus, ^ trifft nur Index 0)
log.match(/^ERROR.*/m);   // ['ERROR boom']

5.2 \b und \B

\b ist eine Null-Breiten-Behauptung: Sie trifft die Position zwischen einem Wortzeichen (\w) und einem Nicht-Wortzeichen. Nützlich für die Ganzwort-Suche:

/\bcat\b/.test('the cat sat');     // true
/\bcat\b/.test('concatenate');     // false

Wortgrenzen sind über \w definiert, was standardmäßig ASCII bedeutet. Chinesisch, Japanisch und Koreanisch setzen keine Leerzeichen zwischen Wörter, deshalb findet \b dort keine Wortkanten. Sie brauchen davor einen Tokenizer (jieba, MeCab), nicht statt eines.

5.3 Multiline-Modus

import re
text = "INFO ok\nERROR fail\nINFO done\n"

re.findall(r'^ERROR.*$', text)              # []
re.findall(r'^ERROR.*$', text, re.MULTILINE) # ['ERROR fail']

In JavaScript liest sich dasselbe als text.match(/^ERROR.*$/gm). Kombinieren Sie m mit g, um jede passende Zeile einzusammeln.

6. Gruppen, Capture und Rückverweise

6.1 Fanggruppen

Klammern haben zwei Aufgaben: Sie gruppieren Teilmuster für Quantoren, und sie fangen den Treffer für die spätere Verwendung ein.

'2026-05-13'.match(/(\d{4})-(\d{2})-(\d{2})/);
// ['2026-05-13', '2026', '05', '13', index: 0, ...]

Gruppen werden von links nach rechts nach ihrer öffnenden Klammer durchnummeriert, beginnend bei 1.

6.2 Nicht-fangende Gruppen

Wenn Sie nur gruppieren, aber nicht einfangen wollen, nutzen Sie (?:...). Das ist schneller und hält die nummerierten Gruppen aufgeräumt:

/(?:https?):\/\/(\S+)/.exec('see https://go-tools.org');
// ['https://go-tools.org', 'go-tools.org']
// das Protokoll ist gruppiert, aber nicht eingefangen; Gruppe 1 ist der Host

6.3 Benannte Gruppen

Benannte Gruppen machen Muster lesbar und refactoring-sicher.

// JavaScript (ES2018+)
const m = '2026-05-13'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups.year;  // '2026'
# Python: beachten Sie die (?P<...>)-Syntax
import re
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-05-13')
m.group('year')  # '2026'

6.4 Rückverweise

Rückverweise lassen einen späteren Teil des Musters wiederholen, was eine frühere Capture gefunden hat.

// Jedes Zeichen finden, das sich direkt hintereinander wiederholt
'bookkeeper'.match(/(\w)\1/g);   // ['oo', 'kk', 'ee']

// Gepaarte HTML-Tags nach Namen abgleichen
const tag = /<(\w+)>(.*?)<\/\1>/;
'<b>bold</b>'.match(tag);
// ['<b>bold</b>', 'b', 'bold']

In Python funktioniert \1 sowohl im Muster als auch im Ersetzungsstring; benannte Rückverweise lauten im Muster (?P=name) und in re.sub-Ersetzungen \g<name>.

7. Lookarounds: Lookahead und Lookbehind

Lookarounds sind Null-Breiten-Behauptungen. Sie prüfen eine Bedingung, ohne Zeichen zu verbrauchen, deshalb darf man sie aneinanderketten.

7.1 Lookahead

// Passwort: mindestens 8 Zeichen, eine Ziffer, ein Großbuchstabe, ein Kleinbuchstabe
const strong = /^(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).{8,}$/;
strong.test('Hunter2!');   // true
strong.test('hunter2!');   // false — kein Großbuchstabe

// Negativer Lookahead: Dateinamen, die nicht .tmp sind
/^[\w-]+(?!\.tmp$)\.[a-z]+$/.test('report.csv'); // true

7.2 Lookbehind

Lookbehind ist das Spiegelbild: Er behauptet, was vor der aktuellen Position steht.

// Einen Preis nach einem Währungssymbol extrahieren: die Zahl behalten, das $ verwerfen
'price: $42.50'.match(/(?<=\$)\d+(\.\d+)?/);   // ['42.50', '.50']

// Negativer Lookbehind: Bond treffen, aber nicht James Bond
'Mr. Bond'.match(/(?<!James )Bond/);  // ['Bond']
'James Bond'.match(/(?<!James )Bond/); // null

7.3 Lookbehind in JavaScript gegen Python

Einer der wenigen Punkte, an denen die beiden Engines so weit auseinanderdriften, dass ein Muster beim Portieren bricht.

EngineLookbehind-Länge
JavaScript (V8, SpiderMonkey, JSC 16.4+)Variable Breite seit ES2018. (?<=\d+) ist gültig.
Python-Standardbibliothek reNur feste Breite. (?<=\d+) löst error: look-behind requires fixed-width pattern aus.
Python-PyPI-Paket regexVariable Breite unterstützt. import regex; regex.search(r'(?<=\d+)abc', '12abc').

Workaround in Python: Schreiben Sie den Lookbehind mit fester Wiederholung um ((?<=\d{3})) oder fangen Sie das Präfix als Gruppe ein und schneiden es nach dem Match ab.

8. Flags und Modifier

8.1 i: Groß-/Kleinschreibung egal

/error/i.test('FATAL ERROR'); // true
re.search(r'error', 'FATAL ERROR', re.IGNORECASE)  # <Match span=(6, 11)>

8.2 m und s

m macht aus ^ und $ Zeilen-Anker. s (dotall) lässt . auch Zeilenumbrüche treffen. Beide sind unabhängig voneinander; kombinieren Sie sie, wenn Sie beides brauchen.

/<script>(.*?)<\/script>/s.exec('<script>\nalert(1)\n</script>')[1];
// '\nalert(1)\n'  (ohne s würde der . die Zeilenumbrüche verweigern)

8.3 g: global

In JavaScript ändert g weniger den Match selbst als die API. Ohne g liefert String.match Fanggruppen; mit g liefert es jede Treffer-Zeichenkette. Nutzen Sie matchAll, um die Fanggruppen über alle Treffer hinweg zu behalten.

const text = 'a=1 b=2 c=3';

text.match(/(\w)=(\d)/);     // erster Treffer mit Gruppen
text.match(/(\w)=(\d)/g);    // ['a=1', 'b=2', 'c=3']  (keine Gruppen)
[...text.matchAll(/(\w)=(\d)/g)]; // jeder Treffer, mit Gruppen

Python kennt kein g. Die globalen Varianten heißen re.findall, re.finditer und re.sub.

8.4 u: Unicode und \p{...}

// Jedes Han-Zeichen treffen (Chinesisch, japanisches Kanji)
/\p{Script=Han}+/gu.test('Hello 世界'); // true

// Emoji treffen (Extended Pictographic)
/\p{Extended_Pictographic}/u.test('👋'); // true

In Python ist Unicode standardmäßig aktiv; re.findall(r'[一-鿿]+', text) ist das Äquivalent für den Han-Bereich. Für die vollständigen Unicode-Property-Escapes nutzen Sie das PyPI-Paket regex: regex.findall(r'\p{Script=Han}+', text).

9. Gängige Muster für den Alltag

9.1 E-Mail-Validierung

Seien Sie ehrlich, welche Variante Sie wirklich brauchen.

// Das 95-%-Muster, das die meisten Formularvalidierer einsetzen
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
email.test('a@b.co');  // true

// Das „Ich will wirklich RFC-5322-nah sein"-Muster
const rfc = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;

Vollständige RFC-5322-E-Mail-Validierung in reinem Regex ist rund 6000 Zeichen lang und scheitert trotzdem an Randfällen. Nutzen Sie das 95-%-Muster und verifizieren Sie danach mit einer Bestätigungs-E-Mail. Etwas anderes prüft am Ende sowieso niemand.

9.2 URL-Extraktion

const urlPattern = /https?:\/\/[^\s<>"]+/g;
const found = 'See https://example.com/a?b=1 and http://x.io'.match(urlPattern);
// ['https://example.com/a?b=1', 'http://x.io']

Nach dem Extrahieren landen Sie meist beim Query-String. Werfen Sie ihn in unseren URL-Dekodierer und -Kodierer, dann lesen Sie prozent-kodierte Parameter auf einen Blick. Wann zu kodieren und wann zu dekodieren ist, klärt der URL-Encoding- und -Decoding-Leitfaden.

9.3 Telefonnummern

// E.164: international, optionales + und 1-3 Ziffern Ländercode
const e164 = /^\+?[1-9]\d{1,14}$/;
e164.test('+14155551234');  // true

// North American Number Plan mit Trennzeichen
const nanp = /^(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
nanp.test('(415) 555-1234'); // true

Sobald die Frage über „Sieht die Form plausibel aus?” hinausgeht, greifen Sie zu libphonenumber. Regex kann nicht prüfen, ob eine Vorwahl tatsächlich existiert.

9.4 IPv4 und IPv6

// IPv4: strikt 0-255 pro Oktett
const ipv4 = /^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$/;
ipv4.test('192.168.1.1');   // true
ipv4.test('999.0.0.1');     // false

// IPv6: vereinfachte Form. Das volle RFC-4291-Muster ist ~600 Zeichen lang.
const ipv6simple = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
ipv6simple.test('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // true

Für echtes IPv6 mit ::-Kurzform, eingebettetem IPv4 und Zone-Identifiers nutzen Sie isIP() aus node:net oder Pythons ipaddress.ip_address(). In reinem Regex ist das ein Initiationsritus, dem unmittelbar eine Wartungslast folgt.

9.5 ISO-8601-Daten und -Zeitstempel

// Nur Datum: YYYY-MM-DD
const isoDate = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
isoDate.test('2026-05-13'); // true

// Datum + Zeit + optionale Sekundenbruchteile + Z oder Offset
const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
iso.test('2026-05-13T09:30:00.123Z'); // true

ISO 8601 sieht einfach aus, steckt aber voller Fallen: Schaltsekunden, Wochendaten (2026-W19), Ordinaldaten (2026-133). Wie sich Epoch-Sekunden zu Millisekunden und Zeitzonenverschiebungen verhalten, klärt der Unix-Zeitstempel-Leitfaden.

10. Suchen-Ersetzen-Workflows mit Regex

10.1 JavaScript: String.replace mit $1

// US-Daten umformatieren: MM/DD/YYYY -> YYYY-MM-DD
'05/13/2026'.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2');
// '2026-05-13'

// Callback nutzen, wenn die Ersetzung bedingt ist
'price 42 dollars'.replace(/(\d+) dollars/, (_, n) => `$${n}`);
// 'price $42'

$1, $2, … verweisen auf nummerierte Gruppen. $<name> verweist auf benannte Gruppen. $& ist der gesamte Treffer; $$ ist ein literales $.

10.2 Python: re.sub mit \1 und Callbacks

import re

# Dieselbe Datumsumformatierung wie oben
re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\1-\2', '05/13/2026')
# '2026-05-13'

# Callback: jede E-Mail-Adresse in einem String in Großbuchstaben umwandeln
def upper_email(m):
    return m.group(0).upper()

re.sub(r'[\w.-]+@[\w.-]+', upper_email, 'mail me at hi@go-tools.org')
# 'mail me at HI@GO-TOOLS.ORG'

In Ersetzungen nutzt Python \1 oder \g<name>. Das Raw-String-Präfix r'...' ist wichtig; ohne es wird \1 zu einem literalen Zeichen.

10.3 CLI: sed, grep, ripgrep, jq

Für Massen-Refactorings auf der Kommandozeile wandert Regex vom Skript in die Shell:

# ripgrep: jedes TODO mit angehängtem Namen finden
rg -n '\bTODO\(([^)]+)\)' --replace 'TODO(\1)'

# grep -E mit Ankern: fehlgeschlagene Login-Zeilen aus auth.log
grep -E '^[A-Z][a-z]{2} +[0-9]+ .*Failed password' /var/log/auth.log

# sed: nachgestelltes Whitespace entfernen, in-place, über einen Baum
find . -name '*.md' -print0 | xargs -0 sed -i -E 's/[[:space:]]+$//'

ripgrep nutzt Rusts regex-Crate (RE2-Stil, lineare Laufzeit, kein Lookbehind). grep -E und sed -E verwenden POSIX Extended Regex, dem \d fehlt; greifen Sie stattdessen zu [0-9] und [[:digit:]]. Sind die Daten JSON, tauschen Sie Regex gegen jq. Der jq-Spickzettel liegt als parallele Referenzkarte daneben.

11. Häufige Fallstricke

11.1 . zu maskieren vergessen

Ein echter Bug aus der Produktion: Ein Log-Redactor sollte IP-Adressen maskieren.

// Falsch: trifft auch '192a168b1c1'
/(\d+).(\d+).(\d+).(\d+)/.test('192a168b1c1');  // true

// Richtig
/(\d+)\.(\d+)\.(\d+)\.(\d+)/.test('192a168b1c1'); // false

Innerhalb einer Zeichenklasse ist . schon literal, deshalb funktionieren [.] und \. beide. Überall sonst müssen Sie es maskieren.

11.2 Gieriges .* frisst zu viel

'<a href="x"><b>bold</b></a>'.match(/<(.*)>/)[1];
// 'a href="x"><b>bold</b></a'  (das ganze Ding!)

Gieriges .* scannt bis zum Ende der Zeichenkette und gibt erst so lange nach, bis > passt: das letzte > in der Eingabe. Entweder wechseln Sie auf faul (.*?) oder, schneller und klarer, nutzen eine negierte Klasse ([^>]*).

11.3 Multiline-Anker

Häufige Verwechslung: ^ und $ treffen standardmäßig keine Zeilenumbrüche. Sie treffen die Positionen am Anfang und Ende der gesamten Eingabe. Erst das m-Flag macht daraus Zeilen-Anker. Erst das s-Flag erlaubt ., Zeilenumbrüche zu überspringen. Beide Flags sind orthogonal, und beim Log-Parsing brauchen Sie meistens beide.

11.4 ReDoS und wie man es entschärft

ReDoS, Regex Denial of Service, ist die Produktions-Variante des katastrophalen Backtrackings. Die Gegenmittel:

  1. Statische Analyse: Tools wie safe-regex, recheck und ESLints no-misleading-character-class fangen gefährliche Muster ab, bevor sie ausgeliefert werden.
  2. Atomare Gruppen (Python regex, PCRE, Ruby, Java): (?>...) verhindert, dass die Engine beim Backtracking wieder in die Gruppe hineinläuft.
  3. Possessive Quantoren (*+, ++, ?+ in PCRE/Java): dieselbe Idee, knappere Syntax.
  4. Auf eine Engine ohne Backtracking wechseln: Gos regexp, RE2, Rusts regex-Crate und das Python-Binding re2 laufen alle in linearer Zeit. ripgrep ist die in der freien Wildbahn am weitesten verbreitete RE2-Anwendung.
  5. Erst die Eingabelänge validieren: Eine 10-KB-Regex-Bombe ist ein Bug; eine 10-Byte-Begrenzung der Eingabe ist eine Zeile Code.

Wer die Alltags-Tools rund um Regex (Formatierer, Decoder, Konverter) auf einen Blick haben will, findet sie im Entwickler-Tools-Leitfaden.

Bevor Sie ein komplexes Muster ausliefern, testen Sie es interaktiv. regex101.com wechselt zwischen den Varianten PCRE, JavaScript, Python und Go, erklärt jeden Token und zeigt das Backtracking, damit Sie katastrophale Muster früh erkennen.

12. FAQ

Was ist der Unterschied zwischen Regex * und +?

* trifft null oder mehr Vorkommen (es kann eine leere Zeichenkette treffen); + trifft eins oder mehr (es braucht mindestens eins). a* trifft '', 'a', 'aaaa'. a+ trifft 'a' und 'aaaa', aber nicht ''.

Wie matche ich mit Regex über mehrere Zeilen hinweg?

Schalten Sie das Multiline-Flag ein (/.../m in JavaScript, re.MULTILINE in Python), damit ^ und $ an jeder Zeile ankern. Damit auch . Zeilenumbrüche überquert, hängen Sie das Dotall-Flag dazu (s in JavaScript, re.DOTALL in Python).

Ist Regex in JavaScript und Python dasselbe?

Die Kernsyntax (Quantoren, Anker, Zeichenklassen, einfache Gruppen) ist zu 90 % identisch. Zwei echte Unterschiede bleiben: JavaScript (ES2018+) unterstützt Lookbehind variabler Länge und schreibt benannte Gruppen als (?<name>...); Pythons Standardbibliothek re verlangt Lookbehind fester Breite und nutzt (?P<name>...). Wer in Python Lookbehind variabler Länge braucht, installiert das regex-Paket von PyPI.

Warum hat meine Regex katastrophales Backtracking?

Sie haben verschachtelte Quantoren mit überlappenden Treffern, etwa (a+)+ oder (a|a)*. Bei Eingaben, die fast passen, aber gegen Ende scheitern, probiert die Engine jede Aufteilung des inneren Quantors aus, also exponentiell viele Pfade. Beheben lässt es sich mit einer atomaren Gruppe (?>a+)+, einem possessiven Quantor a++ oder dem Wechsel auf eine Engine ohne Backtracking wie RE2 oder Gos regexp.

Kann ich Lookbehind in JavaScript benutzen?

Ja. Positiver (?<=...) und negativer (?<!...) Lookbehind sind seit ES2018 in V8 (Chrome, Node.js), SpiderMonkey (Firefox) und JavaScriptCore (Safari 16.4+) verfügbar; Lookbehind variabler Länge ebenfalls. Für ältere Safaris transpilieren Sie mit Babel oder erkennen das Feature per try/catch um new RegExp.

Wie matche ich einen literalen Punkt . in Regex?

Maskieren Sie ihn mit einem Backslash: \. trifft einen literalen Punkt. In einer Zeichenklasse ist der Punkt bereits literal, deshalb funktionieren [.] und [\.] beide. Außerhalb einer Klasse ist ein unmaskiertes . ein Metazeichen mit der Bedeutung „jedes Zeichen außer Zeilenumbruch” (oder jedes Zeichen überhaupt mit dem Dotall-Flag).

Was bedeutet \s in Regex?

\s trifft jedes Whitespace-Zeichen — Leerzeichen, Tab, Zeilenumbruch, Wagenrücklauf, vertikalen Tabulator, Seitenvorschub. Im Unicode-Modus trifft es zusätzlich das geschützte Leerzeichen (NBSP). \S ist die Umkehrung.

Sind reguläre Ausdrücke groß-/kleinschreibungsempfindlich?

Standardmäßig ja. /cat/ trifft Cat nicht. Nutzen Sie das i-Flag in JavaScript (/cat/i) oder re.IGNORECASE bzw. (?i) in Python.

Verwandte Artikel

Alle Artikel anzeigen