Reguläre Ausdrücke zum Ansehen
·
Kursüberblick
1
Die verschwundene Zahlung: Text wörtlich finden
2
Der Fluchtwagen: Zeichenklassen
3
Das zerrissene Telefonbuch: Quantoren
4
Die falschen Anmeldungen: Anker und Validierung
5
Daten in jedem Format: Gruppen und Alternativen
6
Der Zugriff um 3 Uhr nachts: benannte Gruppen und Match-Objekte
7
Die Nacht vor der Story: Ersetzen und Aufteilen
8
Die Herausforderung des Backtrackers: gierig, genügsam, possessiv
9
Die Regeln des Tresors: Lookaround
10
Gleiche Masche: Rückverweise und Bedingungen
11
Der unlesbare Regex: Flags und Kompilieren
12
Die Enttarnung des Backtrackers: Rückblick und ein Tokenizer
CASE-010
Grep & Co. Datendetektei
Fallakte
Auftraggeber
Kommissar Kang
Beweismittel
Drei Drohbriefe an Kommissar Kang und ein vierter, der gestern kam. Die Spurensicherung hat alle vier Zeile für Zeile abgeschrieben.
Zum Abschluss des Falls
Herausfinden, welche Briefe von derselben Person stammen, und die Beweise dafür als Muster zeigen.
Ein verregneter Abend. Kommissar Kang breitet vier Plastikbeutel auf dem Schreibtisch aus. „Drohbriefe. Drei kamen seit letzter Woche, einer gestern. Nachdem der Artikel in der Zeitung stand.“ Die Direktorin rückt ihre Brille zurecht. „Dann ist ein Nachahmer dabei. Beweise hinterlassen Muster. Was dieselbe Hand schreibt, hat dieselben Gewohnheiten.“
Das ist die Abschrift der Spurensicherung, ein Brief pro Zeile. Der eigentliche Text (msg=) ist jeweils ein kurzer englischer Satz, etwa: „Am Freitag fällt der Server“, „Niemand kann mich lesen“, „Wir sehen uns am Ende“, „Zahl jetzt, sonst …“.
#1 2026.10.18 key='K-17' tel=(010)-0000-1018 msg=the server server falls on friday
#2 2026-10-19 key="K-17" tel=010-0000-1019 msg=no one can can read me
#3 2026/10/20 key='K-17' tel=(010)-0000-1020 msg=see you At at the end
#4 2026-10/21 key="K-17' tel=(010-0000-1021 msg=pay now or elseAuf den ersten Blick sehen alle vier gleich aus. Heute spürst du Gewohnheiten mit Rückverweisen auf, einer Syntax, die fragt: „Kommt genau dasselbe, was vorhin gefangen wurde, noch einmal vor?“
Die Direktorin zeigt auf den ersten Brief. „server server. Dasselbe Wort zweimal getippt. Im zweiten Brief steht auch can can.“ Sieht nach einem Tippfehler aus. Aber wenn es in allen drei steckt, ist es eine Gewohnheit.
In Lektion 5 hast du Gruppennummern kennengelernt. Diese Nummern kannst du auch im Muster selbst aufrufen. \1 bedeutet „genau der Text, den Gruppe 1 gerade gefangen hat“. Das nennt man Rückverweis (backreference).
import re
line = "msg=the server server falls on friday"
m = re.search(r"\b(\w+)\s+\1\b", line)
print(m.group()) # server server
print(m.group(1)) # server(\w+) das Wort server, dann steht \1 ab diesem Moment für den Text server.\1 heißt nicht, dass das Muster \w+ noch einmal angewendet wird. Deshalb passt es nicht auf zwei verschiedene Wörter.re.search(r"(\w+)\s+(\w+)", "the server").groups() # ('the', 'server')
re.search(r"(\w+)\s+\1", "the server") # NoneAuch die \b vorn und hinten sind wichtig. Ohne sie gilt sogar das can can in can cannot als „wiederholtes Wort“.
re.search(r"(\w+)\s+\1", "can cannot") # <re.Match object; span=(0, 7), match='can can'>
re.search(r"\b(\w+)\s+\1\b", "can cannot") # NoneBeweislabor
re.search(r"", text)
Rückgabewert
Im Ergebnis des Labors fällt etwas auf: Das At at aus dem dritten Brief fehlt. A und a sind eben verschiedene Zeichen. Mit re.I (Groß-/Kleinschreibung ignorieren, offiziell in Lektion 11) vergleicht auch der Rückverweis ohne Rücksicht auf Groß- und Kleinschreibung.
re.findall(r"\b(\w+)\s+\1\b", letters) # ['server', 'can']
re.findall(r"\b(\w+)\s+\1\b", letters, re.I) # ['server', 'can', 'At']
print(re.search(r"(a)\1", "aA", re.I)) # <re.Match object; span=(0, 2), match='aA'>letters ist hier der String mit den vier Briefzeilen von oben. Das Ergebnis zeigt: Die Gewohnheit, ein Wort doppelt zu tippen, findet sich nur in den Briefen 1, 2 und 3.
In Lektion 5 hat die Direktorin ein Problem vertagt. Wählt man das Trennzeichen mit [-./], kommen auch gemischte Daten wie 2026-10/20 durch, weil die Klasse an jeder Stelle unabhängig wählt. Fang das erste Trennzeichen mit einer Gruppe ein und verlang an der zweiten Stelle per Rückverweis dasselbe Zeichen.
re.fullmatch(r"\d{4}[-./]\d\d[-./]\d\d", "2026-10/21") # <re.Match object; span=(0, 10), match='2026-10/21'>
re.fullmatch(r"\d{4}([-./])\d\d\1\d\d", "2026-10/21") # None
re.fullmatch(r"\d{4}([-./])\d\d\1\d\d", "2026/10/20") # <re.Match object; span=(0, 10), match='2026/10/20'>Auch benannte Gruppen (Lektion 6) kannst du im Muster aufrufen, mit der Syntax (?P=name). Bei vielen Gruppen sind Namen leichter zu lesen als Nummern.
DATE = r"(?P<y>\d{4})(?P<sep>[-./])(?P<m>\d\d)(?P=sep)(?P<d>\d\d)"
m = re.search(DATE, "#3 2026/10/20 key='K-17'")
print(m.groupdict()) # {'y': '2026', 'sep': '/', 'm': '10', 'd': '20'}(?P=sep) genauso \2 schreiben.(?P=sep), im Ersatz-String mit \g<sep> (Lektion 7), am Match-Objekt mit m["sep"].Beweislabor
re.search(r"", text)
Rückgabewert
Schau dir die Werte nach key= an. In Brief 1 und 3 stehen sie in einfachen Anführungszeichen, 'K-17', in Brief 2 in doppelten, "K-17". Nur Brief 4 öffnet mit einem doppelten und schließt mit einem einfachen: "K-17'. Fängst du das öffnende Zeichen in einer Gruppe und verlangst beim Schließen \1, erkennst du, ob die Paare zusammenpassen.
QUOTED = r'(["\']).*?\1'
for line in letters.splitlines():
m = re.search(QUOTED, line)
print(line[:2], m.group() if m else None)
# #1 'K-17'
# #2 "K-17"
# #3 'K-17'
# #4 None\'. Im Regex ist \' einfach ein '..*? (genügsamer Quantor, Lektion 8). Mit .* würde vom ersten Anführungszeichen bis zum letzten gleichen alles auf einmal gefressen.re.search(r'(["\']).*\1', '"a" and "b"').group() # '"a" and "b"'
re.search(r'(["\']).*?\1', '"a" and "b"').group() # '"a"'Beweislabor
re.search(r"", text)
Rückgabewert
Steckst du dasselbe Muster aber in findall, kommt etwas Seltsames heraus.
re.findall(QUOTED, letters) # ["'", '"', "'"]
[m.group() for m in re.finditer(QUOTED, letters)] # ["'K-17'", '"K-17"', "'K-17'"]Das ist genau die Regel aus Lektion 5: Gibt es eine Gruppe, liefert findall nur diese Gruppe. Und diesmal kannst du sie auch nicht durch (?:...) ersetzen, denn dann gäbe es keine Gruppe mehr, auf die \1 verweisen könnte.
Dots Tipp
Ich habe auch mal Anführungszeichenpaare mit findall eingesammelt und dann nur eine Pfote voll Anführungszeichen angeschleppt. Für einen Rückverweis brauchst du unbedingt eine Gruppe. Wenn du den ganzen Treffer willst, nimm finditer und hol dir m.group(). Du könntest das ganze Muster auch noch einmal in Klammern packen, aber dann verrutschen die Nummern, und aus \1 wird \2.
Jetzt die Telefonnummern hinter tel=. In Brief 1 und 3 steht die Vorwahl in Klammern, etwa (010)-0000-1018, in Brief 2 ohne Klammern. Brief 4 hat nur eine öffnende Klammer: (010-0000-1021. Die Regel „Wenn eine öffnende Klammer da war, muss auch eine schließende kommen“ schreibst du als bedingtes Muster.
(?(1)yes|no) heißt: „Hat Gruppe 1 am Treffer teilgenommen, dann gilt das Muster yes, sonst das Muster no.“ Den no-Teil kannst du weglassen. (?(1)yes) heißt: „War die Gruppe dabei, dann yes, sonst wird gar nichts verlangt.“
PHONE = r"(\()?\d{2,3}(?(1)\))-\d{3,4}-\d{4}"
for tel in ["(010)-0000-1018", "010-0000-1019", "(010-0000-1021", "010)-0000-1021"]:
print(tel, bool(re.fullmatch(PHONE, tel)))
# (010)-0000-1018 True
# 010-0000-1019 True
# (010-0000-1021 False
# 010)-0000-1021 False(\()?: fängt die öffnende Klammer als Gruppe 1, sie darf aber auch fehlen.(?(1)\)): Wurde Gruppe 1 gefangen, wird eine schließende Klammer verlangt, sonst geht es einfach weiter.Bei benannten Gruppen setzt du die Bedingung mit dem Namen statt der Nummer: (?(name)yes|no). Im folgenden Beispiel lautet die Regel: Eine Listennummer wie (1) muss mit einer schließenden Klammer enden, eine ohne Klammer wie 2. mit einem Punkt.
ITEM = r"(?P<p>\()?\d+(?(p)\)|\.)"
for s in ["(1)", "2.", "(3.", "4)"]:
print(s, bool(re.fullmatch(ITEM, s)))
# (1) True
# 2. True
# (3. False
# 4) FalseBeweislabor
re.search(r"", text)
Rückgabewert
Selbst wenn alle Fallbedingungen erfüllt sind: In der Zeile von Brief 4 ist im Labor immer noch 010-0000-1021 markiert. search und finditer dürfen an jeder beliebigen Stelle mit dem Treffer beginnen, also überspringen sie die öffnende Klammer und fangen dahinter an. Stell tel= davor, um die Startposition festzunageln.
re.search(PHONE, "tel=(010-0000-1021").group() # '010-0000-1021'
re.search(r"tel=" + PHONE + r"\b", "tel=(010-0000-1021") # NoneBei zweistelligen Rückverweisen wird es verwirrend. \10 im Muster heißt nicht „Gruppe 1, gefolgt vom Zeichen 0“, sondern Gruppe 10. Gibt es keine Gruppe 10, ist das ein Fehler. Als Oktalzeichen wird es auch nicht gelesen.
re.compile(r"(a)\10")
# re.PatternError: invalid group reference 10 at position 4
p = re.compile(r"(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)\10")
print(p.fullmatch("abcdefghijj")) # <re.Match object; span=(0, 11), match='abcdefghijj'>\1[0].\g<1>0 aus dem Ersatz-String (Lektion 7) funktioniert im Muster nicht. Im Muster ist \g ein unbekanntes Escape und ergibt einen Fehler.\0 beginnen oder dreistellig sind (\101 ist A). Mehr dazu in Lektion 12.print(re.fullmatch(r"(a)\1[0]", "aa0")) # <re.Match object; span=(0, 3), match='aa0'>Du schreibst die vier Gewohnheiten als Regeln auf und zählst, wie viele davon auf jeden Brief zutreffen. Die Schlüssel sind koreanisch: 반복 = Wiederholung, 날짜 = Datum, 따옴표 = Anführungszeichen, 괄호 = Klammer.
RULES = {
"반복": re.compile(r"\b(\w+)\s+\1\b", re.I),
"날짜": re.compile(r"\d{4}([-./])\d\d\1\d\d"),
"따옴표": re.compile(r'key=(["\']).*?\1'),
"괄호": re.compile(r"tel=(\()?\d{2,3}(?(1)\))-\d{3,4}-\d{4}\b"),
}
for line in letters.splitlines():
hits = [name for name, rx in RULES.items() if rx.search(line)]
print(line[:2], hits)
# #1 ['반복', '날짜', '따옴표', '괄호']
# #2 ['반복', '날짜', '따옴표', '괄호']
# #3 ['반복', '날짜', '따옴표', '괄호']
# #4 []Die Briefe 1, 2 und 3 teilen alle vier Gewohnheiten. Ein Wort wird doppelt getippt, das Trennzeichen bleibt immer dasselbe, und bei Anführungszeichen und Klammern geht kein einziges Paar daneben. Brief 4 ahmt nur das Äußere nach und hat keine einzige der Gewohnheiten.
Kommissar Kang schnalzt mit der Zunge. „Nummer 4 ist also ein Spaßvogel, der den Artikel gelesen hat, und echt sind nur die drei.“ Die Direktorin nickt. „Der Echte ist penibel. Bei dem geht kein Paar daneben.“
Fall abgeschlossen — Die Drohbriefe 1, 2 und 3 stammen von derselben Person (dem Backtracker). Doppelte Wörter, gleiche Datumstrennzeichen und passende Paare bei Anführungszeichen und Klammern stimmen überein. Brief 4 wurde als Nachahmung eingestuft und separat an Kommissar Kang übergeben.
Gerade als du den Bericht zuklappen willst, greift die Direktorin noch einmal nach den Originalbriefen. Es gab Zeilen, die die Spurensicherung nicht abgeschrieben hat. Ganz unten auf jedem der drei echten Briefe steht eine seltsame Zeile, nur aus Klammern und Backslashes.
0000. Tipp: Auch \1 kann einen Quantor bekommen.<b>bold</b> <i>x</b> nur die Stellen, an denen öffnendes und schließendes Tag denselben Namen haben. Als Ergebnis soll nur <b>bold</b> herauskommen.<, muss sie mit > enden. <kim@example.com> und kim@example.com sollen passen, <kim@example.com nicht.Die Spur des Backtrackers
(a+)+$
Die letzten Zeilen der drei Briefe. Hängt sie der Reihe nach aneinander. Es wird eine Zeile. Falls ihr sie lesen könnt.
Das Notizbuch der Direktorin
\1 im Muster ist genau der Text, den Gruppe 1 tatsächlich gefangen hat. Das Muster wird nicht noch einmal angewendet.
Doppelte Wörter: \b(\w+)\s+\1\b. Gleiches Datumstrennzeichen: \d{4}([-./])\d\d\1\d\d.
Benannte Gruppen ruft man mit (?P=name) auf. Im Ersatz mit \g<name>, am Match mit m['name'].
Anführungszeichenpaar: (["']).*?\1. findall liefert nur die Anführungszeichen, für den ganzen Treffer finditer.
Bedingt: (?(1)yes|no), (?(name)yes|no). no darf fehlen: (?(1)yes).
Unter re.I ignoriert auch der Rückverweis die Groß-/Kleinschreibung. \10 ist Gruppe 10, Gruppe 1 gefolgt von 0 ist \1[0].
← Zurück
Lektion 9 · Die Regeln des Tresors: Lookaround
Weiter →
Lektion 11 · Der unlesbare Regex: Flags und Kompilieren
Über neue Lektionen informiert werden
Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.
R
ReflexKR
Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.
ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee
Hinweis zu Affiliate-Links© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Fragen & Antworten
Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.