Regex-Spickzettel: Muster, die jeder Entwickler braucht

10 min14. Mai 2026

Grundbausteine: Zeichen und Quantifier

Reguläre Ausdrücke bestehen aus normalen Zeichen (Literalen) und Metazeichen mit besonderer Bedeutung. Die wichtigsten Metazeichen sind: . (beliebiges Zeichen außer Newline), ^ (Anfang), $ (Ende), * (0 oder mehr), + (1 oder mehr), ? (0 oder 1), und \ (Escape-Zeichen).

Quantifier bestimmen, wie oft ein Element vorkommen darf. {n} bedeutet genau n-mal, {n,m} bedeutet zwischen n und m Mal, {n,} bedeutet mindestens n-mal. Standardmäßig sind Quantifier gierig (greedy) — sie matchen so viel wie möglich. Ein nachgestelltes ? macht sie genügsam (lazy): .*? matcht so wenig wie möglich.

Zeichenklassen in eckigen Klammern definieren eine Menge erlaubter Zeichen: [abc] matcht a, b oder c. [a-z] matcht jeden Kleinbuchstaben. [^abc] negiert die Klasse — matcht alles außer a, b, c. Vordefinierte Klassen sparen Tipparbeit: \d = [0-9], \w = [a-zA-Z0-9_], \s = Whitespace.

Gruppen mit runden Klammern () erfassen Teile des Matches für spätere Verwendung (Backreferences mit \1, \2 usw.). Nicht-erfassende Gruppen (?:...) gruppieren ohne zu erfassen — nützlich wenn du nur die Alternation brauchst aber keinen Capture.

# Quantifier-Übersicht
a*        → "", "a", "aa", "aaa", ...    (0 oder mehr)
a+        → "a", "aa", "aaa", ...        (1 oder mehr)
a?        → "", "a"                       (0 oder 1)
a{3}      → "aaa"                         (genau 3)
a{2,4}    → "aa", "aaa", "aaaa"          (2 bis 4)
a{2,}     → "aa", "aaa", "aaaa", ...     (mindestens 2)

# Greedy vs Lazy
".*"      → matcht "hello" "world" komplett (greedy)
".*?"     → matcht "hello" und "world" einzeln (lazy)

Anchors und Boundaries

Anchors matchen keine Zeichen, sondern Positionen im String. ^ matcht den Anfang des Strings (oder der Zeile im Multiline-Modus), $ matcht das Ende. \b ist eine Wortgrenze — die Position zwischen einem \w-Zeichen und einem \W-Zeichen.

Wortgrenzen sind extrem nützlich für Suche-und-Ersetzen: \bclass\b findet "class" als ganzes Wort, aber nicht "className" oder "subclass". Ohne \b würdest du ungewollt Teile anderer Wörter matchen — ein klassischer Regex-Fehler bei Refactoring-Operationen.

Im Multiline-Modus (Flag m) matcht ^ den Anfang jeder Zeile und $ das Ende jeder Zeile, nicht nur Anfang/Ende des gesamten Strings. Das ist relevant wenn du Logdateien oder mehrzeilige Texte parsen willst.

Weniger bekannt: \A und \Z (in Sprachen die es unterstützen wie Python und Ruby) matchen absoluten String-Anfang/-Ende, unabhängig vom Multiline-Flag. In JavaScript gibt es diese Anchors nicht — dort verwendest du ^ und $ ohne m-Flag für denselben Effekt.

Lookahead und Lookbehind

Lookaheads und Lookbehinds (zusammen "Lookarounds") prüfen ob ein Muster vor oder nach der aktuellen Position existiert, ohne es in den Match einzubeziehen. Sie sind "zero-width assertions" — sie verbrauchen keine Zeichen.

Positiver Lookahead (?=...) prüft ob das Muster folgt: \d+(?=€) matcht Zahlen gefolgt von €, aber das €-Zeichen ist nicht Teil des Matches. Negativer Lookahead (?!...) prüft ob das Muster NICHT folgt: \d+(?!€) matcht Zahlen die nicht von € gefolgt werden.

Lookbehind funktioniert umgekehrt: (?<=€)\d+ matcht Zahlen denen € vorausgeht. Negativer Lookbehind (?<!...) prüft Abwesenheit. Wichtig: In JavaScript sind Lookbehinds erst seit ES2018 verfügbar und werden in älteren Browsern (IE11) nicht unterstützt.

Praktischer Einsatz: Passwort-Validierung mit mehreren Bedingungen gleichzeitig. (?=.*[A-Z])(?=.*[0-9])(?=.*[^a-zA-Z0-9]).{8,} prüft ob der String mindestens einen Großbuchstaben, eine Ziffer und ein Sonderzeichen enthält — alles in einem Ausdruck durch gestapelte Lookaheads.

// Lookahead: Zahlen vor "px" finden (ohne "px" im Match)
'font-size: 16px; margin: 8px;'.match(/\d+(?=px)/g);
// → ["16", "8"]

// Lookbehind: Zahlen nach "€" finden
'Preis: €49.99, Rabatt: €10'.match(/(?<=€)\d+\.?\d*/g);
// → ["49.99", "10"]

// Negativer Lookahead: Wörter die NICHT von ":" gefolgt werden
'key: value other word'.match(/\b\w+\b(?!:)/g);
// → ["value", "other", "word"]

// Passwort-Validierung mit gestapelten Lookaheads
const strongPassword = /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[^a-zA-Z\d]).{12,}$/;
strongPassword.test('Sicher3s#Passwort');  // true

Die wichtigsten Praxis-Muster

E-Mail-Validierung: Die RFC-5322-konforme Regex hat über 6.000 Zeichen — verwende sie nicht. Für Frontend-Validierung reicht: ^[^\s@]+@[^\s@]+\.[^\s@]{2,}$ — das filtert offensichtlich ungültige Eingaben und lässt den Server die echte Validierung machen (Bestätigungsmail senden).

URL-Erkennung: https?:\/\/[^\s<>"]+ ist ein pragmatischer Ansatz für die meisten Fälle. Perfekte URL-Validierung per Regex ist praktisch unmöglich (IPv6-Adressen, internationalisierte Domains, etc.) — verwende die URL-Klasse des Browsers: new URL(str) wirft bei ungültigen URLs eine Exception.

Deutsche Postleitzahlen: ^\d{5}$ — einfach, aber effektiv. Für strengere Validierung prüfe ob die Zahl zwischen 01001 und 99998 liegt (serverseitig). Schweizer PLZ: ^\d{4}$. Österreichische PLZ: ^\d{4}$.

Datumsformat DD.MM.YYYY: ^(0[1-9]|[12]\d|3[01])\.(0[1-9]|1[0-2])\.(19|20)\d{2}$ prüft grob gültige Tage und Monate, fängt aber nicht ungültige Kombinationen wie 31.02.2026 ab. Für echte Datumsvalidierung verwende eine Date-Bibliothek — Regex kann keine Schaltjahre berechnen.

// Pragmatische E-Mail-Validierung
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;

// Deutsche Telefonnummer (mit optionaler Landesvorwahl)
const phoneRegex = /^(\+49|0049|0)[1-9]\d{1,14}$/;

// IPv4-Adresse
const ipv4Regex = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;

// Hex-Farbcode (#RGB oder #RRGGBB)
const hexColorRegex = /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/;

// Slug (URL-freundlicher String)
const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;

Flags und ihre Wirkung

Flags (auch Modifier genannt) ändern das Verhalten der gesamten Regex. In JavaScript stehen sie nach dem schließenden Slash: /muster/flags. Die wichtigsten: g (global — alle Matches finden, nicht nur den ersten), i (case-insensitive), m (multiline — ^ und $ matchen Zeilenanfänge/-enden).

Das s-Flag (dotAll, seit ES2018) lässt . auch Newline-Zeichen matchen. Ohne s-Flag matcht . alles außer \n. Das ist relevant beim Parsen von mehrzeiligem HTML oder Konfigurationsdateien, wo Inhalte über mehrere Zeilen gehen.

Das u-Flag (Unicode) aktiviert korrekte Unicode-Behandlung. Ohne u-Flag zählt JavaScript Surrogate-Paare (Emojis, etc.) als zwei Zeichen. Mit u-Flag funktionieren \p{...}-Unicode-Properties: \p{Letter} matcht jeden Buchstaben in jeder Sprache, \p{Emoji} matcht Emojis.

Das v-Flag (UnicodeSets, seit ES2024) erweitert u um Set-Notation in Zeichenklassen: [\p{Letter}&&\p{Script=Latin}] matcht nur lateinische Buchstaben. Außerdem ermöglicht es Subtraction: [\w--\d] matcht Wortzeichen ohne Ziffern. Noch nicht in allen Laufzeitumgebungen verfügbar.

// Flags in der Praxis
'Abc ABC abc'.match(/abc/gi);     // ["Abc", "ABC", "abc"]

// Unicode-Properties (erfordert u-Flag)
'Ärger 2026 🚀'.match(/\p{Letter}+/gu);  // ["Ärger"]
'Ärger 2026 🚀'.match(/\p{Number}+/gu);  // ["2026"]
'Ärger 2026 🚀'.match(/\p{Emoji}/gu);    // ["🚀"]

// dotAll-Flag für mehrzeilige Matches
const html = '<div>\n  Inhalt\n</div>';
html.match(/<div>.*<\/div>/s);   // matcht den gesamten Block
html.match(/<div>.*<\/div>/);    // null (ohne s matcht . kein \n)

Named Groups und moderne Regex-Features

Named Capture Groups geben Gruppen sprechende Namen statt Nummern: (?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2}) extrahiert Datumsteile mit result.groups.year, result.groups.month, result.groups.day. Das macht den Code deutlich lesbarer als \1, \2, \3.

String.prototype.matchAll() (ES2020) liefert einen Iterator über alle Matches inklusive Capture Groups — etwas, das vorher nur mit einer while-Schleife und exec() möglich war. Perfekt für das Extrahieren aller Vorkommen eines Musters mit ihren Untergruppen.

String.prototype.replaceAll() mit Regex erlaubt komplexe Transformationen: text.replaceAll(/(?<word>\w+)/g, (_, word) => word.toUpperCase()). Ohne g-Flag wirft replaceAll() einen TypeError — ein häufiger Stolperstein.

Atomare Gruppen und possessive Quantifier (?>...) bzw. a++ sind in JavaScript nicht verfügbar, aber in Sprachen wie Java, .NET und Perl. Sie verhindern Backtracking und schützen vor katastrophalem Backtracking (ReDoS). In JavaScript erreichst du ähnliches durch sorgfältiges Pattern-Design oder die Verwendung einer Timeout-Logik.

// Named Groups für Datum-Parsing
const dateRegex = /(?<day>\d{2})\.(?<month>\d{2})\.(?<year>\d{4})/;
const match = '25.12.2026'.match(dateRegex);
console.log(match.groups);
// { day: "25", month: "12", year: "2026" }

// matchAll für mehrere Treffer mit Groups
const text = 'Preis: 49,99€ und 19,99€';
const priceRegex = /(?<amount>[\d,]+)€/g;
for (const m of text.matchAll(priceRegex)) {
  console.log(m.groups.amount);  // "49,99", dann "19,99"
}

// replaceAll mit Transformation
'hello-world-test'.replaceAll(
  /(?<=-)(?<char>[a-z])/g,
  (_, char) => char.toUpperCase()
);  // "hello-World-Test"

Performance und Sicherheit: ReDoS vermeiden

Reguläre Ausdrücke können exponentielles Backtracking verursachen — ein Sicherheitsrisiko namens ReDoS (Regular Expression Denial of Service). Das passiert wenn ein Muster verschachtelte Quantifier hat wie (a+)+$ oder (a|a)+$. Bei bestimmten Eingaben explodiert die Laufzeit exponentiell.

Faustregel: Vermeide verschachtelte Quantifier auf überlappenden Mustern. (.*a){10} ist gefährlich. Auch Alternationen mit Überlappung sind riskant: (a|ab)+ kann bei langen Strings ohne Match extrem langsam werden, weil die Engine alle Kombinationen durchprobiert.

Schutzmaßnahmen: Begrenze die Eingabelänge bevor du die Regex ausführst. In Node.js kannst du re2 verwenden — eine Regex-Engine von Google die lineare Zeitkomplexität garantiert (kein Backtracking). In Cloudflare Workers ist re2 der Standard.

Teste deine Regex mit gezielt bösen Eingaben: Wiederhole problematische Muster (aaaaaaaaaaaa!) und miss die Ausführungszeit. Tools wie regex101.com zeigen den Backtracking-Aufwand an. Unser Regex-Tester warnt bei potenziellen Performance-Problemen.

Regex in verschiedenen Sprachen: Unterschiede

JavaScript: Keine possessiven Quantifier, keine atomaren Gruppen, kein \A/\Z. Seit ES2018 Lookbehind, seit ES2024 v-Flag. Regex-Literale /.../ und new RegExp("...") — bei letzterem musst du Backslashes doppelt escapen (\\d statt \d).

Python: re-Modul mit ähnlicher Syntax wie JavaScript. Zusätzlich: (?P<name>...) für Named Groups (alternativ die JS-Syntax (?<name>...) seit Python 3.7). re.VERBOSE-Flag erlaubt Kommentare und Whitespace im Pattern für bessere Lesbarkeit.

Java/Kotlin: Voller PCRE-Support inklusive possessiver Quantifier (a++) und atomarer Gruppen (?>...). Backslashes müssen in String-Literalen doppelt escaped werden: Pattern.compile("\\d+"). Raw-Strings in Kotlin vermeiden das Problem.

Go: Verwendet RE2-Syntax — garantiert lineare Laufzeit, unterstützt aber keine Lookaheads/Lookbehinds und keine Backreferences. Das ist eine bewusste Design-Entscheidung für Performance und Sicherheit. Wenn du Lookarounds brauchst, musst du die Logik anders lösen.