Mustererkennung mit Regular Expressions (RegEx)

https://oer-informatik.de/regex

https://bildung.social/@oerinformatik/110397061085890869

tl/dr; (ca. 20 min Lesezeit): Die Mustererkennung Regulärer Ausdrücke (RegEx) ermöglicht Automatisierungen an Stellen, wo Handarbeit mühsam und langwierig ist. Telefonnummern in unterschiedlichen Formaten gespeichert? In einer CSV-Datei alle Spalten in verkehrter Reihenfolge? Wo stecken im Fließtext Geldbeträge mit Nachkommastellen? Derlei Aufgaben lassen sich mit RegEx lösen - besser man kennt sie! (Zuletzt geändert am 23.05.2023)

Was sind Reguläre Ausdrücke?

Ein Regulärer Ausdruck (RegEx) ist eine Zeichenkette, die Muster in Zeichenketten mithilfe von syntaktischen Regeln beschreibt.

Reguläre Ausdrücke helfen vor allem dabei, Teilstrings zu finden oder zu ersetzen, die einem einheitlichen Muster entsprechen, sich aber nicht durch einfache Wildcards (wie z.B. ? oder *) ausdrücken lassen.

So lassen sich beispielsweise relativ einfach Muster bilden, die in einem vorhandenen langen Text alle Geldbeträge, Telefonnummern, Internetlinks, EAN-Nummern oder E-Mail-Adressen finden.

Mit RegEx wird man zum Superhelden! (von xkcd, CC BY-NC 2.5 https://xkcd.com/208/)
Mit RegEx wird man zum Superhelden! (von xkcd, CC BY-NC 2.5 https://xkcd.com/208/)

Ein Einstiegs-Beispiel für eine RegEx: die Klassenbezeichnungen an unserer Schule folgen der Logik:

1. Großbuchstabe: Abteilung
2.+3. Großbuchstabe: Ausbildungsberuf
4. Zahl: Einschulungsjahr (letzte Stelle)
5. Großbuchstaben für Parallelklassen
==> 3 Großbuchstaben, eine Zahl, ein Großbuchstabe

Ein Regulärer Ausdruck, der das beschreiben würde, wäre beispielsweise:

[A-Z][A-Z][A-Z][0-9][A-Z]

Es wurden einfach fünf Zeichenklassen hintereinander geschrieben. Das geht noch einfacher:

[A-Z]{3}[0-9][A-Z]
  • [A-Z] definiert hier die Zeichenklasse der Großbuchstaben (alle Zeichen, die in der ASCII/Unicode-Tabelle zwischen A und Z stehen)

  • [0-9] die Zeichenklasse der Zahlen (ASCII zwischen 0 und 9)

  • {3} definiert das mehrfache Vorkommen der Zeichenklasse (ein Quantifizierer).

Suchmuster bestehen also in erster Linie aus Aneinanderreihungen von Zeichenklassen und Quantifizierern.

Literale und Metacharaktere als Elementarbausteine

In Regulären Ausdrücken wird zwischen Literalen und Metacharakteren unterschieden: Literale repräsentieren das jeweilige Zeichen, Metacharaktere haben eine syntaktische Bedeutung. Alle Zeichen, die Metacharaktere repräsentieren, lassen sich über einen Backslash maskieren und repräsentieren so das eigentliche Zeichen (z.B. \[ für die eckige Klammer).

Zeichen Metacharakter
Bedeutung innerhalb
einer Zeichenklasse [ ]
Metacharakter
Bedeutung außerhalb
einer Zeichenklasse
. beliebiges Zeichen
^ Negierung Beginn der Eingabe
$ Ende der Eingabe
? 0-1mal Quantifizierer
(? ...) leitet Direktiven ein
* 0-n mal (Quantifizier)
+ 1-n mal (Quantifizier)
| oder
[ Klassenbeginn
] Klassenende
{ } von - bis mal (Quantifizierer)
( ) Gruppierung
\ Maskierung Maskierung
/ Begrenzung der RegEx
- Bereichs-kennzeichnung

Immer dann, wenn für ein Zeichen in der obigen Tabelle eine Metacharakterbedeutung zugewiesen ist, muss das Zeichen maskiert werden, wenn es als Literal gesucht werden soll.

Lazy / Greedy

Reguläre Ausdrücke mit den Quantifizierern * und + sind gierig (greedy): sie versuchen, so viel wie möglich Treffer zu erzielen und das Suchmuster über möglichst viele Zeichen auszudehnen. In der Regel ist dies jedoch nicht gewünscht: Bestimmte Zeichenklassen sollen sich nur so lange wiederholen, bis das erste Vorkommen der folgenden Zeichenklasse auftritt. Diese genügsamere Suchcharakteristik (lazy) lässt sich umstellen, in dem die lazy Varianten der Multiplizität genutzt werden (*? und +?): Das Fragezeichen kommt hier als Metacharakter erneut zum Zug:

Beispieltext greedy RegEx lazy RegEx
<kunden><kunde></kunde></kunden> <.*>
findet Gesamttext
<.*?><br/ findet nur <kunden>
Es gibt kein richtiges Leben im falschen E.+n
findet Gesamttext
E.+?n
findet Es gibt kein
dadada (da){1,}
findet dadada
(da){1,}?
findet drei mal da

Übersicht vordefinierter Zeichenklassen

Definition von Zeichenklassen

RegEx Erläuterung
[abc],[a-z],[A-Z],
[0-9],
[a-zA-Z0-9]
Zeichenklassen passen auf ein Zeichen, das in der Klasse enthalten ist.
[abc] passt auf a, b oder c, aber nicht auf x. Sie können auch Bereiche angeben,
[a-z] passt auf alle Kleinbuchstaben.
[a-zA-Z0-9] Bereiche können auch kombiniert werden.
Die Sonderzeichen verlieren in einer Klasse ihre Wirkung: Punkt, Stern, Fragezeichen (siehe oben: Metacharaktere)
[^abc],
[^a-z],
[^a-zA-Z0-9]
^ negiert die Zeichenklassen:
[^abc] passt auf jedes beliebige Zeichen, außer a, b und c.
(!! außerhalb der [ ] steht ^ für Eingabebeginn!! s.o.)
[a-z&&[^mn]] Verschachtelung und Ausschluss: a bis z außer m und n

Vordefinierte Zeichenklassen

RegEx Erläuterung
. Der Punkt passt auf ein beliebiges Zeichen (bei vielen Parsern: bis auf Newline)
\d = [0-9]
\D = [^0-9]
digit: Vordefinierte Zeichenklassen für Ziffern. \d passt auf alle Ziffern, entspricht also genau [0-9]. (\D ist genau das Gegenteil und passt auf alles außer Ziffern.)
\t, \n, \r Tabulator, linefeed (Zeilenvorschub, LF), carriage return (Wagenrücklauf, CR)
*nix nutzen \n als Zeilenumbruch, Windows \r\n
\s = ^\S whitespace: \s ist eine vordefinierte Klasse, die auf Whitespaces passt, also Leerzeichen, Zeilenumbruch, Tabulator usw. [\t\n\x08\x0c\r] (\S ist wieder das Gegenteil)
\w = [a-zA-Z_0-9]= ^\W word: \w entspricht [a-zA-Z_0-9], ob Umlaute erkannt werden können, hängt von der Systemkonfiguration ab (\W ist die Negation davon)
\b = ^\B Wortgrenze, also Tabulator, Leerzeichen
\xhh Zeichen mit Hexadezimalwert hh
\uhhhh
\N{EURO SIGN}
Unicodezeichen mit Hexcode hhhh bzw. in Unicode-Notation

Position und Grammatik (außerhalb der Zeichenklassendefinition!)

RegEx Erläuterung
^ start of string anchor: Anfang der Eingabe. ^ana passt in ananas, aber nicht in banane.
$ end of string anchor: Ende der Eingabe. ze$ passt in Katze, aber nicht in Katzen.
\A \Z Stringbeginn und -ende
\< \> Wortbeginn und -ende
\| Oder-Verknüpfung, entweder der Ausdruck links oder der Ausdruck rechts muss passen.
() Mit Klammern gruppierte und selektierte Reguläre Ausdrücke können in der Reihenfolge gezielt ausgegeben werden: Ausgabe im RegEx über \1 (erste Klammer) \2 usw… (parserabhängig auch $1 $2 usw.) oder per Java über den matcher.group(1)
(?<wert>) Selektierte Ausdrücke können auch mit einem Namen versehen werden (hier: wert ) und über diesen im Matcher (z.B. Java) gezielt angesprochen werden: (?<wert>[0-9.,]+)\\s*(?<waehrung>[A-Z]{3})
matcher.group("wert")
(?:) Ausdruck wird gruppiert, ist aber nicht selektiv ansprechbar. (geht bei wenigen Parsern)
(?=) Ausdruck: “Muss gefolgt werden von” Ausdruck vor der Klammer muss gefolgt werden von Ausdruck in der Klammer. Letzterer wird aber nicht in das Ergebnis geschrieben.
(?!) Ausdruck: “Darf nicht gefolgt werden von” (entsprechend wie oben)

Quantifizierer: Häufigkeit des Auftretens (außerhalb der Zeichenklassendefinition!)

RegEx Erläuterung
* Das vorangehende Element (ein einzelnes Zeichen, eine geklammerte Zeichenfolge oder eine Zeichenklasse) darf beliebig oft vorkommen, auch keinmal.
+ Das vorangehende Element muss mindestens einmal, darf aber auch öfter vorkommen.
? Das vorangehende Element darf nicht oder genau einmal vorkommen, aber keinesfalls öfter.
{n} Das vorangehende Element muss genau n-mal vorkommen.
{n,} Das vorangehende Element muss mindestens n-mal vorkommen.
{n, m} Das vorangehende Element muss mindestens n-mal aber höchstens m-mal vorkommen.
{,m} Das vorangehende Element darf höchstens m-mal vorkommen. (geht bei wenigen Parsern)

POSIX-Zeichenklassen

RegEx Erläuterung
\p{Lower} ASCII Kleinbuchstabe: [a-z]
\p{Upper} ASCII Großbuchstabe: [A-Z]
\p{ASCII} ASCII Zeichen: [\x00-\x7f]
\p{Alpha} Buchstabe: [A-Za-z]
\p{Digit} Ziffer: \d
\p{Alnum} Buchstabe oder Ziffer

Unicode-Zeichenklassen

RegEx Erläuterung
\p{Lu} Unicode Großbuchstabe
\p{Ll} Unicode Kleinbuchstabe
\p{Sc} Unicode Währungssymbol
\p{Nl} Unicode Zahl oder Buchstabe

Flags

RegEx Erläuterung
(?i) Case-insensitive (ASCII)
(?iu) Case-insensitive (Unicode)
(?g) global match
(?m) Multi-line mode
(?s) Single-line / dotall mode

Beispiele zum Verständnis

  • CSBME findet exakt die Zeichenkette CSBME
  • ^CSBME$ findet eine Zeile, die ausschließlich aus der Zeichenkette CSBME besteht
  • (Hund|Katze|Maus) findet die Wörter Hund oder Katze oder Maus
  • [Hund|Katze|Maus] die Buchstaben HKMadenstuz sowie die Pipe
  • [0-9]{2}-[0-9]{2}-[0-9]{4} findet ein Datum der Notation 01-01-2000
  • suche: ([0-9]{2})-([0-9]{2})-([0-9]{4})
    ersetze: \1\.\2\.\3
    wandelt ein Datum von der Form 01/01/2000 in die Form 01.01.2000 um.

RegEx in Programmiersprachen nutzen:

Text suchen

Die Klasse String bietet mir der Methode matches() die Möglichkeit, unmittelbar zu prüfen, ob ein bestimmtes Muster vorliegt:

Teilstrings tauschen

Man kann aber nicht nur prüfen, ob bestimmte Muster vorkommen, man kann auch konkrete Muster ersetzen (mit der String-Methode replace()):

Komplexere Pattern mit Matches und LookingAt

Komplexere als die oben genannten Beispiele lassen sich über die Klassen Pattern und Matcher realisieren, die für ein Muster und einen Prüfer stehen. Zunächst die bekannte Darstellung (Zeile „boolean …“) , darunter dasselbe Problem mit Pattern und Matcher umgesetzt.

Group

Die Vorteile von Pattern/Matcher lassen sich insbesondere ausspielen, wenn mehrere Teilbereiche von einer Zeichenkette gesucht sind. Die eingeklammerten Bereiche der RegEx können gezielt (über matcher.group()) angesprochen und ausgegeben werden:

Split

Regular Expressions können auch genutzt werden, um Zeichenketten in (mehrere) Teilzeichenketten zu teilen:

Find

Sofern ein Suchmuster öfters gefunden wurden, können alle Ergebnisse mit Hilfe einer while-Schleife durchlaufen werden:

Replace

Auch das eingangs erwähnte tauschen von Zeichenketten kann für komplexere Probleme mit Pattern/Matcher umgesetzt werden:

RegEx in Powershell und Bash

Mit RegEx über das Terminal nach Dateiinhalten suchen

Beispiel: in einer Datei befindet sich irgendwo ein ungewöhnliches Zeichen, weswegen die Datei nicht verarbeitet werden kann. Folgende Aufrufe mit regulären Ausdrücken finden die betreffenden Zeilen:

Mit RegEx über das Terminal Dateiinhalte verändern

In einer Textdatei soll bei allen Geldbeträgen, die mit EUR enden das Zeichen € gesetzt werden. Da im Text auch an anderen Stellen EUR steht, muss sichergestellt werden, dass nur bei den Geldbeträgen die Währungsbezeichnung ersetzt wird. Folgender Aufruf ändert das direkt in der Datei:

Noch kein Beispiel für die Powershell vorhanden

RegEx in SQL-Abfragen

PostgreSQL bietet mit den Funktionen regexp_instr() und regexp_like() Möglichkeiten, die Mustererkennung komfortabel durchzuführen. Weitere Infos dazu unter diesem Link.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: Mustererkennung mit Regular Expressions (RegEx)” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/regex veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/regex/regex-basics. Stand: 23.05.2023.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: