Inhalt

Reguläre Ausdrücke - Wenn sie nur nicht so praktisch wären

Ich habe lange einen Bogen um reguläre Ausdrücke gemacht. Jedes Mal wenn ich es mit dem Thema zu tun bekam war die instinktive Reaktion die Flucht. Ich habe mich immer mal wieder versucht diesem Thema zu nähern. Die ganzen schlauen Menschen, die reguläre Ausdrücke beherrschen, können ja nicht ganz falsch liegen. Ich habe allen Mut zusammen genommen und mich durchgekämpft. Ich muss ganz ehrlich zugeben ich bin immer noch nicht der allergrößte Fan regulärer Ausdrücke. Trotzdem, sie sparen viel Zeit.

Das geht bei ganz einfachen Beispielen los. Stell Dir vor Du hast in Google Sheets eine lange Tabelle mit Artikelnummern. Die Artikelnummern haben eine einheitliche Struktur (z.B. artikel-farbe-12587). Was machst Du, wenn Du z.B. die Reihenfolge der Segmente ändern möchtest? Mit ein paar  einfachen Formeln und hier und da eine Hilsspalte lässt sich das lösen 😁.

Mit einem regulären Ausdruck kann man dieses Problem  wesentlich eleganter lösen. Google Sheets besitzt eine Funktion REGEXREPLACE(). Damit kann man dieses Problem mit einer simplen Formel lösen. Bevor wir aber zur Lösung kommen müssen wir ein paar Grundlagen kennen.

Die Grundlagen für reguläre Ausdrücke

Reguläre Ausdrücke helfen  bei der Erkennung von Mustern innerhalb von Zeichenketten. Das kleinste Element einer Zeichenkette ist ein einzelnes Zeichen. Damit fangen wir an. Es gibt bei der Verwendung regulärer Ausdrücke verschiedene sog. Zeichenklassen:

Zeichenklassen

RegEx Bedeutung
. Jedes beliebige Zeichen
\d  Ziffern (0-9)
\D  Alle Zeichen ausgenommen Ziffern
\s  Leerzeichen (Leerzeichen, Tab, CR, LF
\S  Alle Zeichen, die kein Leerzeichen sind
\w  alphanumerische Zeichen inklusive
\W  jedes Zeichen, dass kein alphanumerisches Zeichen inklusive

Damit können wir z.B. schon einmal ein Datumsmuster suchen:

\d\d.\d\d.\d\d\d\d

Zwei Ziffern, gefolgt von einem Punkt, gefolgt von 2 Ziffern, einem Punkt und 4 Ziffern. 

Der Punkt bekommt hier einen \ vorangestellt. Dadurch wird klar gemacht, dass wir nicht jedes beliebige Zeichen meinen, sondern tatsächlich den Punkt. Genauso funktioniert das auch bei den Ziffern. Wäre der \ nicht vor dem d würde nach dem Buchstaben d gesucht werden und nicht nach der Zeichenklasse.

Besondere Zeichen

Neben den geläufigen Zeichen wie Buchstaben, Zahlen und Satzzeichen gibt es Sonderzeichen.

Regex Bedeutung
c z.B. der Buchstabe “c”
^ Zeilenanfang / Negation bei [^..] Zeichenklassen
$ Ende der Zeile oder Zeichenkette
\ hebt die spezielle Bedeutung des nächsten Zeichens auf
\n LF Vorschub in die nächste Zeile / Zeilenumbruch
\r CR oder WR - Rückbewegung der Schreibbewegung auf Pos.1 der gleichen Zeile
\r\n Zeilenumbruch DOS / Windows
\t Tabulator
\f FF oder Seitenumbruch - Bewegung in die erste Zeile der nächsten Seite
\a Beep oder Piep
\e Escape
\b leere Zeichenkette am Wortanfang oder Wortende
\B leere Zeichenkette nicht am Anfang oder Ende des Wortes
< leere Zeichenkette am Wortanfang
> leere Zeichenkette am Wortende

Eigene Zeichenklassen festlegen

Man kann auch eigene Zeichenklassen festlegen:

Regex Bedeutung
[abc] a, b, oder c - eine sog. einfache Klasse
[^abc] jedes Zeichen, dass nicht a, b, oder c ist
[a-h]       Zeichenbereich von a bis h
[a-h]'[r-u] Zeichen im Bereich zwischen a bis h oder r bis u

In einer  Zeichenklasse werden entweder einzelne Zeichen definiert [aeiou] oder ein Bereich [a-h0-9]. Mit dem ^ kann man die Klasse negieren, d.h. [^abc] bedeutet jedes Zeichen, dass nicht ein a, b oder c ist. Außerdem kann man mehrere Zeichenklassen mit dem ' Operator (oder) verbinden.

Angaben von Mengen

Man kann in regulären Ausdrücken angeben wie oft ein Zeichen darf.

Regex Bedeutung
a?   einmal oder gar nicht
a*   gar nicht bis beliebig oft
a+   einmal bis beliebig oft
a{3} genau dreimal
a{3,5}  dreimal oder mehr, aber maximal fünfmal

Damit kann man z.B. Nummernblöcke suchen: \d{3}-\d{4}-\d{5} findet Nummern, die so formatiert sind: 123-4567-89101.

Gierige und träge Quantifizierer

Bei der Angabe von Mengen in regulären Ausdrücken gibt es sogenannte träge und gefräßige Quantifizierer. Dabei versuchen  gierige Quantifizierer so viel wie möglich pro Ergebnis zu verarbeiten. Ihre trägen Kollegen wollen so wenig wie möglich pro Ergebnis verarbeiten .

Träge Quantifizierer:

Regex Bedeutung
a*?      gar nicht bis so selten wie möglich
a+?       einmal bis so selten wie möglich
a{3,}?    dreimal oder mehr, aber so wenig wie möglich

Gierige Quantifizierer:

Regex Bedeutung
a*+ gar nicht bis so oft wie möglich 
a++ einmal bis so so oft wie möglich 
a{3,}+ dreimal oder mehr, aber so oft wie möglich

Nehmen wir einmal an wir haben den Satz Hallo -Bob-, wie geht es -Dir-?
Und jetzt suchen wir einmal mit einem gierigen und einem trägen Quantifizierer:

(gieriger Quantifizierer): -.*- findet: -Bob-, wie geht es -Dir-

(träger Quantifizierer): -.*?- findet: -Bob- -Dir-

Man sieht, dass der gierige Quantifizierer im gleichen Satz einen langen Treffer findet. Der träge Quantifizierer findet stattdessen zwei kurze Treffer.

Gruppen

Man kann Teile eines regulären Ausdrucks von einander abgrenzen. Dabei werden Gruppen in einem regulären Ausdruck durch Klammern definiert. Gruppen innerhalb eines regulären Ausdrucks erhalten eine Nummer. Die Nummerierung so, dass der komplette gefundene Ausdruck die 0 erhält. Danach werden die jeweiligen Gruppen hochgezählt werden.


Beispiel Artikelnummer:

artikel-rot-2545

regulärer Ausdruck: (\w+)-(\w+)-(\d+)


Der reguläre Ausdruck findet die komplette Artikelnummer. Es werden aber auch 4 Nummern zugewiesen. Für den Abruf verwendet man $n also $ + der jeweiligen Nummer. In diesem Beispiel sieht das wie folgt aus:


$0 = artikel-rot-2545

$1 = artikel

$2 = rot

$3 = 2545


Nun haben wir alles was wir für unser Ursprungsproblem brauchen. Die Formel in Google Sheets sieht wie folgt aus:


=REGEXREPLACE(A1;"(\w+)-(\w+)-(\d+)";“$3-$2-$1”)


Beispiel: http://Google Sheets

Das gleiche Prinzip lässt sich in diversen Programmiersprachen anwenden. Dabei gib es leichte Unterschiede je nach Sprache:

Python:
re.sub(r’(\w+) (\w+)',r’\2 \1’,‘Wort1 Wort2’)

Go:
regex := regexp.MustCompile(`(\w+) (\w+)`)
fmt.Printf(regex.ReplaceAllString(“Wort1 Wort2”, “$2 $1”))

Javascript:
let regex = /(\w+) (\w+)/;
“Wort1 Wort2”.replace(regex, “$2 $1”);

alternativ auch so in Javascript:

let regex2 = new RegExp("(\w+) (\w+)");
“Wort1 Wort2”.replace(regex2, “$2 $1”);

Lookaround

Jetzt wird es etwas komplizierter 😇. Man kann auch den Kontext eines regulären Ausdrucks angeben. D.h. wir können ganz konkret nach etwas suchen, dass sich vor oder hinter einer bestimmten Zeichenfolge befindet.

Look behind

Beispiel Artikelliste:
artikel-rot-2538
artikel-gelb-2539
artikel-blau-2542
artikel-lila-2543
artikel-rot-2545
artikel-gelb-2546

Regulärer Ausdruck + Look behind:


(?<=artikel-)\w+

Der 1. Ausdruck artikel- muss dem 2. Ausdruck \w+ vorausgehen

(?<!-)\d+
Der 1. Ausdruck - darf dem 2. Ausdruck \d+ nicht vorausgehen


Beispiel zum ausprobieren:
Look behind 1: https://regexr.com/4irf2
Look behind 2: https://regexr.com/4irf8

Look ahead

Beispiel Artikelliste:
artikel-rot-2538
artikel-gelb-2539
artikel-blau-2542
artikel-lila-2543
artikel-rot-2545
artikel-gelb-2546

Look ahead: 


\w+(?=-)

Der 2. Ausdruck - muss dem 1. Ausdruck \w+ folgen 

\w+(?!-)

Der 2. Ausdruck - darf dem 1. Ausdruck \w+ nicht vorausgehen


Beispiel zum ausprobieren:
Look ahead 1: https://regexr.com/4irg3
Look ahead 2: https://regexr.com/4irg9

Der Unterschied zwischen den beiden Arten von Lookarounds ist die Richtung. Im Grunde muss man es sich so vorstellen:

Zuerst wird nach dem regulären Ausdruck gesucht. Ein Look ahead untersucht, ob sich vor dem Ergebnis das zweite angegebene Suchmuster befindet. Bei Look behind wird entsprechend dahinter gesucht. Ist die zusätzliche Bedingung erfüllt wird das Ergebnis zurückgegeben.

Beispiel regulärer Ausdruck

Zum Abschluss noch ein gängigeres Beispiel. Der folgende reguläre Ausdruck ist eine von vielen Methoden um eine E-Mail Adresse zu erkennen.

Vollständiger regulärer Ausdruck:

\w+[.-]?\[email protected]\w+([.-]?\w+)?.[a-zA-Z]{2,4}

Zerlegen wir das einmal in seine Bestandteile:


\w+
mindestens ein bis beliebig viele alphanum. Zeichen inkl. _

[.-]?
“.” oder “-”, gar nicht bis max. 1x vorkommend (also optional) 

\w+
mindestens ein bis beliebig viele alphanum. Zeichen inkl. _ 

@
das @ Zeichen 

\w+  mindestens ein bis beliebig viele alphanum. Zeichen inkl. _

([.-]\w+)?
eine Gruppe die gar nicht bis 1x vorkommt (optional): [.-] ein “.” oder “-”, danach \w+, also mindestens ein bis beliebig viele alphanum. Zeichen inkl. _

.
der . vor der Domainendung 

[a-zA-Z]{2,4}
mindestens 2 maximal 4 Buchstaben in Groß- oder Kleinschreibung


Und hier nochmal der Link auf regexr: https://regexr.com/4itu0

Mein Fazit

Mittlerweile finde ich reguläre Ausdrücke eigentlich ziemlich praktisch. Sie sind ein sehr mächtiges Werkzeug, dass einem vieles erleichtern kann. Ich will auf keinen Fall behaupten, dass ich reguläre Ausdrücke mittlerweile liebe. Ich habe aber gelernt mit ihnen umzugehen. Mein Tip für alle die sich mit dem Thema befassen wollen: Beispiele sind alles! Um reguläre Ausdrücke zu verstehen reicht es nicht sich einfach nur diesen oder einen anderen Text durchzulesen. Ihr müsst so viel wie möglich ausprobieren um wirklich zu verstehen wie reguläre Ausdrücke funktionieren.