R

ReflexKR

Reflex-Dokumentation

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

Reguläre Ausdrücke zum Ansehen
Lektion 10

Zeit 7:30–8:20

Gleiche Masche: Rückverweise und Bedingungen

CASE-010

Grep & Co. Datendetektei

Fallakte

Gleiche Masche

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 else

Auf 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?“

10-1. Schon Gefangenes noch einmal finden: \1 (10 Min.)

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
  • Fängt (\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")               # None

Auch 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")   # None

Beweislabor

re.search(r"", text)

Rückgabewert

Das Muster fängt gerade zwei beliebige Wörter. Ersetze das zweite \w+ durch \1, damit nur Stellen gefunden werden, an denen dasselbe Wort zweimal steht.

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.

10-2. Die Hausaufgabe aus Lektion 5: dasselbe Trennzeichen erzwingen (8 Min.)

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'}
  • Benannte Gruppen haben auch eine Nummer, also könntest du statt (?P=sep) genauso \2 schreiben.
  • Je nach Ort ruft man dieselbe Gruppe anders auf: im Muster mit (?P=sep), im Ersatz-String mit \g<sep> (Lektion 7), am Match-Objekt mit m["sep"].

Beweislabor

re.search(r"", text)

Rückgabewert

Das ist das Datumsmuster aus Lektion 5. Setz das erste [-./] in Klammern und ersetze das zweite [-./] durch \1, damit Daten mit gemischten Trennzeichen durchfallen.

10-3. Öffnende und schließende Anführungszeichen (7 Min.)

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 Python-String müssen beide Arten von Anführungszeichen stehen, deshalb steht dort \'. Im Regex ist \' einfach ein '.
  • Die Mitte ist absichtlich .*? (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

Das Muster wählt öffnendes und schließendes Anführungszeichen unabhängig voneinander, also kommen auch falsche Paare durch. Fang das öffnende in einer Gruppe und ersetze das schließende durch \1. Wenn alles passt, schau dir auch das Ergebnis von findall an.

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.

10-4. Gruppe gefangen? Dann dies, sonst das: bedingte Muster (10 Min.)

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) False

Beweislabor

re.search(r"", text)

Rückgabewert

Im Muster wissen \(? und \)? nichts voneinander, deshalb kommt es auch mit nur einer Klammer durch. Fang die öffnende Klammer mit (\()? ein und ersetze die schließende durch (?(1)\)).

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")  # None

10-5. Welche Gruppe ist \10? (5 Min.)

Bei 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'>
  • Willst du „Gruppe 1, dann das Zeichen 0“, trennst du die 0 ab, indem du sie in eine Klasse steckst: \1[0].
  • Das \g<1>0 aus dem Ersatz-String (Lektion 7) funktioniert im Muster nicht. Im Muster ist \g ein unbekanntes Escape und ergibt einen Fehler.
  • Oktalzeichen sind es nur, wenn sie mit \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'>

Den Fall lösen (10 Min.)

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.

Übungen

  1. Schreib ein Muster, das Stellen findet, an denen dieselbe Ziffer viermal hintereinander steht, wie 0000. Tipp: Auch \1 kann einen Quantor bekommen.
  2. Finde in <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.
  3. Schreib als bedingtes Muster: Beginnt eine E-Mail-Adresse mit <, 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.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex