Bis das Gehirn blutet: Der Regex-Guide von t3n
Regex ist die Sprache, die Sprachen bändigt – und die Entwickler manchmal zum Weinen bringt. Trotzdem kommt man um sie nicht herum, denn Reguläre Ausdrücke könnt ihr immer dann einsetzen, wenn ihr nach einem gewissen Muster in Zeichenketten beziehungsweise Texten (zum Beispiel Substrings) sucht oder ihr Text modifzieren wollt. Das Herausfiltern von Beistrichen oder das Finden von Postleitzahlen und Telefonnummern in einer Textdatei mit mehreren 1.000 Einträgen wird so quasi zum Kinderspiel – vorausgesetzt, ihr beherrscht die Regulären Ausdrücke.
Wir haben schon einen einleitenden Artikel zu Regex veröffentlicht, in dem ihr unter anderem auch Ressourcen findet, die euch beim Erlernen von Regex helfen sollen. Die Regulären Ausdrücke sind selbst eine Aneinanderreihung von Zeichen, bei deren Anzahl es technisch keine Begrenzung gibt. In der Regel sollten Regex-Anweisungen aber kurz gehalten werden, damit die ohnehin schwer zu lesende Sprache nicht noch unverständlicher wird. Dabei könnt ihr euch die Regex-Anweisung als kleines Programm vorstellen, das in Regex geschrieben wurde – wobei der Umfang der Syntax recht klein und domain-spezifisch ist. Daher ist es möglich, dass ihr mit Regex auch If/Then/That
-Anweisungen umsetzt. Wobei ihr natürlich auf korrekte Syntax und die spezifischen Eigenarten von Regex achten müsst – aber dazu mehr in diesem Guide.
Regex: tl;dr
Die folgende Liste soll als kurze Referenz dienen, damit ihr schnell den ein oder anderen Befehl nachschauen könnt. Die einzelnen Befehle werden im Laufe des Guides genauer erklärt:
Literale: a b c d e f 1 2 3 4 5 6
etc.
Metacharacters: . [ ] { } ? * + | ( ) ^ $
Zahl: d
Alphanumerischer Character: w
Whitespace (Leertasten, Tabulatoren, „Carriage-Return“ und „Line-Feeds“): s
Negiert die Character-Klassen: D, W, S, [^abc]
Character: .
Multiplier: {42}
, {4,20}
Eins oder mehr: +
Null oder Eins: ?
Null oder mehrere: *
Grouping/Alternation: (Mon|Diens)tag
Wort- und Text-Boundaries: b ^ $
Escape:
In die Höhle des Löwen: Die Regulären Ausdrücke
Die Regulären Ausdrücke setzen sich aus Literalen und „Metacharacters“ zusammen. Der Unterschied liegt dabei darin, dass die „Metacharacters“ nicht dazu genutzt werden, die Zeichen darzustellen, die gefunden werden sollen, sondern um bestimmte Anweisungen durchzuführen – daher besitzen sie eine spezielle Bedeutung.
Regex: Das Literal
Die meisten Zeichen (a b c d e
et cetera und Zahlen wie 1 2 3 4 5 6
et cetera) sind Literale, sie finden sich also selbst. Wenn ihr also nach t3n
sucht, dann bedeutet das: „Finde t
, gefolgt von einer 3
, gefolgt von einem n
.
Metacharacter in Regex
Wie bereits angesprochen gibt es einige Zeichen (. [ ] { } ? * + | ( ) ^ $
), die spezielle Fähigkeiten oder Eigenschaften aufweisen. Diese werden als Metacharacter bezeichnet, wobei nicht alle in einer Charakter-Klasse verwendet werden können, aber dazu später mehr.
.
Der Punkt beziehungsweise „Fullstop“ oder „Dot“ findet ein Zeichen. Der reguläre Ausdruck t.n
bedeutet: „Finde das Zeichen t
gefolgt von einem beliebigen Zeichen und einem abschließenden n
.“ Damit finden wir t3n
, aber auch ten
oder tnn
. Wir bekommen aber nicht t33n
oder tn
zurück. Anders verhält es sich mit Leertasten. Der reguläre Ausdruck t n
bedeutet: „Finde t
gefolgt von einer Leertaste, gefolgt von einem n
.
Aber was ist, wenn ihr etwas finden wollt und der .
soll nicht als Anweisung, sondern als Literal erkannt werden? Die Antwort heißt: „Escape“. Ihr könnt „Metacharacters“ zu Literalen umwandeln, indem ihr vor dem Zeichen einen setzt. Der reguläre Ausdruck
t.n
bedeutet: „Finde t
gefolgt von .
gefolgt von n
. Und da selbst auch zu den „Metacharacters“ zählt, könnt ihr auch diesen „escapen“:
t
.n
„Character class range“
Angenommen, ihr wollt ein Literal einer gewissen Menge aus dem Alphabet suchen, dann könnt ihr [a b c d e f]
et cetera verwenden. Um das aber zu verkürzen, könnt ihr in Regulären Ausdrücken auch eine „Range“ angeben. Das letzte Beispiel als „Range“ angegeben sieht so aus: [a-f]
. Dieses Regex bedeutet nichts anderes als: „Finde eines der Zeichen aus [a b c d e f]
“. Ihr könnt zwischen [A-Z]
, [a-z]
, [1-9]
oder Teilmengen davon unterscheiden. Der Bindestrich hat außerhalb der „Character class range“ keine spezielle Bedeutung, somit gibt a-z
einfach nur ein a
gefolgt von einem Bindestricht gefolgt von einem z
zurück.
Dabei ist zu beachten, dass „Ranges“ zusammen mit isolierten Zeichen zusammen funktionieren können: [0-9.,]
gibt euch eine Zahl oder .
oder ,
zurück. Wichtig: „Ranges“ funktionieren nur für Zeichen, nicht aber für Ziffern beziehungsweise Zahlen. Der reguläre Ausdruck [1-21]
bedeutet nicht: „Finde Ziffern von 1
bis 21
“ sondern „Finde die Ziffern 1
oder 2
oder 1
.
„Character class negation“
Aber Regex kann nicht nur finden, sondern auch nicht finden – also negieren. Wenn ihr also nach einem Zeichen sucht, das nicht t
sein soll, dann lautet der Ausdruck dafür: [^t]
„Freebie character classes“
Wie auch schon bei „Ranges“ bieten die „Freebie character classes“ eine vereinfachte Schreibweise für eine gewisse Menge an. Wenn ihr also nach einer Ziffer suchen wollt, dann könnt ihr das mit [0-9]
– oder aber ihr sucht nach d
. Dasselbe gilt auch für Wörter. Anstatt nach alphanumerischen Zeichenketten mit [0-9A-Za-z_] zu suchen, könnt ihr mit [w]
dasselbe erreichen. Aber Achtung: Enthalten Wörter Sonderzeichen wie Accents, also zum Beispiel „Chérie“, werden sie nicht mehr als Wort erkannt. Mit s
sucht ihr übrigens nach „Space-Characters“ wie zum Beispiel der Leeraste, dem Tabulator-Zeichen, Zeilenumschaltungen und dem „Line-Feed“.
Wollt ihr negieren, so wird kein ^
davor gesetzt, sondern der Metacharacter groß geschrieben, also findet D
alles außer Ziffern und W
alles außer Wörtern et cetera. Um nach einem Datum im Format YYYY-MM-DD zu suchen, müsst ihr also nicht [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
nutzen, sondern ihr könnt auch dddd-dd-dd
eingeben. Wie ihr seht: Es gibt mehrere Varianten, Regex zu nutzen.
Mulitpliers
Ihr könnt jetzt die Basisfunktionen von Regex nutzen, aber durch die „Multipliers“ werden eure regulären Ausdrücke effizienter – und das machen die geschwungenen Klammern {}
möglich.
Wenn ihr nach a
suchen wollt, könnt ihr einfach das Literal a
nutzen – oder ihr nutzt einen „Multiplier“ von 1. Dementsprechend würde a{1}
genau a
finden. Natürlich ist ein „Multiplier“ sinnvoller, wenn ihr nach sich wiederholenden Zeichen oder Zeichenketten sucht. So würde a{3}
nach aaa
suchen. Aber Vorsicht: [abc]{3}
bedeutet, dass nach dem karthesischen Produkt von a
oder b
oder c
– also aaa
, aab
, aac
, aba
, abb
, abc
, baa
, bba
, bbb
, bbc
et cetera gesucht wird und nicht nur nach aaa
oder bbb
oder ccc
.
Multiplier ranges
Ihr könnt eure „Multiplier“ aber auch mit „Ranges“ bereichern. Das ist praktisch, wenn ihr zum Beispiel nach Wörtern sucht, die im britischen Englisch anders geschrieben werden als im Amerikanischen: colou{0,1}r
– gibt euch „colour“ oder „color“ zurück. Ihr sucht also nach u
in der Range von 0
Zeichen und 1
Zeichen.
Dabei ist zu beachten, dass t{10}
das gleiche ist wie t{10,10}
und dass der Ausdruck t{1,4} bedeutet: „Finde ein tttt
gefolgt von ttt
, gefolgt von tt
, gefolgt von t
“. Desweiteren ist es wichtig zu wissen, dass „Multipliers“ gierig sind. Sollte der zu durchsuchende String zum Beispiel 42tttt84
enhalten, dann wird der reguläre Ausdruck auf tttt
„matchen“ – es wird also nicht nach einem t
gestoppt.
Obwohl der „Multiplier“ gierig ist, wird ein perfekter „Match“ nicht ignoriert. Wenn also die Zeichen-Sequenz so aussieht: t42ttt
, wird zuerst das erste t
gefunden. Erst nach einer weiterführenden Suche wird ttt
gematcht.
Die „Ranges“ können auch offen sein: t{1,}
bedeutet „Finde ein oder mehrere t
in einer Reihe“ – wobei der Multiplier gierig bleibt. Mit .{0,}
könntet ihr den gesamten Text ausgeben lassen, denn diese Range bedeutet: „Finde alles – auch leere Strings“.
Freebie Multipliers
Wie bereits erwähnt könnt ihr „Multiplier Ranges“ verwenden, um Begriffsvariationen wie color
und colour
zu finden. Der „Freebie Multiplier“ ?
hat dieselbe Bedeutung wie {0,1}
, somit folgt, dass colou?r
nichts anderes bedeutet als: „Finde colour
oder color
“.
*
ist dasselbe wie eine offene „Range“{0,}
, also könnt ihr „Finde Alles“ auch mit .*
ausdrücken.
Die offene „Range“ {1,}
kann mit +
ausgedrückt werden. Um also genau eine Zahl zu finden könnt ihr d+
nutzen. Zum Unterschied dazu und als Wiederholung:
?*+
findet:?
, gefolgt von*
, gefolgt von+
. Dermaskiert die „Metacharacters“
- Mit einer „Character class range“
[?*+]
sucht ihr nach „Finde?
oder*
oder+
Alternation
Ähnlich wie in anderen Programmiersprachen bedeutet |
– also die „Pipe“ – ein logisches ODER. Regex bietet euch allerdings mehrere verschiedene Einsatzmöglichkeiten:
t3n|ten
bedeutet: „Finde t3n
oder ten
. Wobei t|3|n
dasselbe – begründet durch die „Character Class Range“ – ist wie [t3n]
.
Grouping
Regex kann auch gruppieren. Sollte es komplizierter werden als t3n|ten
, wenn ihr also beispielsweise ein Bier aus einer Bier-Liste finden wollt, dann könnt ihr das „Grouping“ einsetzen. Um also ein Bier zu finden, könnt ihr mit (Schwarz|Malz|Weiß|Dunkel|Alt)bier
danach suchen. Wobei (w*)bier
alle Wörter zurückliefert, die auf bier
enden.
Ihr könnt auch Sub-Groups beziehungsweise „Capture Groups“ anlegen – wie zum Beispiel (w) wollen heute ((w+) w+)
was euch Wir wollen heute Bier trinken
zurück gibt. Die einzelnen „Capture Groups“ geben folgendes aus:
- Capture-Group 1:
Wir
- Capture-Group 2:
Bier trinken
- Capture-Group 3:
Bier
Word boundaries
Die „Word boundary“ sind der Raum zwischen einem alphanumerischen Zeichen beziehungsweise einem „Wort-Character(s)“ – also w
– und einem Nicht-Wort-Characters – also W
. Wobei der Beginn und das Ende als „Word boundaries“ mitgezählt werden: t3n Magazin
hat also vier „Word boundaries“. Dabei ist zu beachten, dass „Word boundaries“ so nicht sichtbar sind, weil sie eine Breite von 0
besitzen und sie auch keine „Characters“ sind.
Line boundaries
Alpha und Omega treffen auch auf Zeilen zu, sie haben einen Anfang und ein Ende, eben das gleiche Prinzip wie bei „Word boundaries“. Mit Regex könnt ihr diese mit ^
und $
selektieren. Wobei ^
den Bereich unmittelbar vor der Zeile und $
den Bereich unmittelbar am Ende der Zeile markiert.
Eine leere Zeile könnt ihr also mit ^$
finden – Zeilenbeginn und Zeilenende, wobei dazwischen nichts ist. Beachte: Um ^
zu finden, müsst ihr mithilfe einer „Character class negation“ das Zeichen wie folgt maskieren: [^]
. Da ^
in eckigen Klamern sonst einer Verneinung gleich kommt.
Wie auch die „Word boundaries“ sind die „Line boundaries“ keine Zeichen im eigetnlichen Sinne und sie Verfügung über eine Breite von Null. Wobei in verschiedenen Implementierungen auch A
und z
existieren, die nicht den Beginn und das Ende einer Zeile, sondern des Textes definieren.
Ich hoffe, dieser Guide hilft dem einen oder anderen, die Regulären Ausdrücke besser zu verstehen. Wie häufig nutzt ihr Regex?
Durchlebt der Autor noch seine Pubertät oder weshalb hält er es für witzig genug, Brüste als Titelbild fürn Artikel über Regex zu wählen?
Es wird aber auch nur noch gemeckert in den Comments ;D
Nehmts mal ein bischen lockerer.
Ich lerne regex immer wieder neu wenn ich sie brauche ;)
Irgendwie ist das wie mit purem SQL. Eigentlich kann ich es , aber zur Sicherheit schlag ich immer nochmal nach.
Der pure Nerd-faktor – Regex-Ausdrücke visualisieren:
atom.io/packages/regex-railroad-diagram
– Björn Dorra, Founder http://netzaktiv.de
Danke für den Tipp, das ist wirklich nützlich! Schade, dass es das Tool nur für Atom gibt — obwohl ich ja kurz auf Sublime Text gehofft hatte :)
Gibt’s auch ähnlich im Browser: http://www.regexper.com
Ich will mal eine Lanze für RegEx brechen, die sind ein super Hilfsmittel und auch nicht schwerer zu erlernen als manche Programmiersprache.
Und mit den vielen Testtools, die es gibt, auch für Einsteiger gut nutzbar.
Ich verwende ganz gerne Patterns auf OSX
„Most people, when confronted with a problem, think: ‚I know, I’ll use regular expressions!‘ Now they have two problems.“
„Ich lerne regex immer wieder neu wenn ich sie brauche ;)“
Geht mir genauso. Das ‚Problem‘ an RegEx ist doch eigentlich, das man es viel zu selten benötigt/anwendet, als das es sich lohnt es wirklich auswendig zu lernen.
OT: Ist es eigentlich schlimm, wenn man erst mit dem Lesen dieses Artikels merkt, das t3n L33tspeak für Ten ist? (Ich lese t3n erst seit ein paar Monaten)
Nein, das ist nicht schlimm. Es ist aber falsch. ;)
Der Name „t3n“ stammt aus alten Zeiten. Damals stand t3n für „Typo 3 News“.
Guten Morgen.
Spannender Artikel und interessanter Input bzw. spannende Gedanken.
Danke für!.
Grüße.
Das ganze hat nichts mit Nerd zu tun, daß ganze ist einfach nur eine Grundvoraussätzung für 2 Berufe (Entwickler / Admin).
Wer dies nicht beherrscht ist kein Entwickler oder Admin.
Ich würde mal sagen: „Möchte gern etwas von dem“.
—
Nur weil nen Koch schneller seine Zwiebeln schneller schneidet oder blind weiss wann was fertig ist, ist er auch kein Nerd.
—
Ich bin beim Lesen über die folgende Stelle gestolpert:
„Dabei ist zu beachten, dass t{10} das gleiche ist wie t{10,10} und dass der Ausdruck t{1,4} bedeutet: „Finde ein tttt gefolgt von ttt, gefolgt von tt, gefolgt von t“.“
Im ersten Augenblick dachte ich, wieso soll t{1,4} das gleiche sein wie t{10}:
Finde „tttt“ gefolgt von „ttt“, gefolgt von „tt“, gefolgt von „t“ heißt ja nichts anderes als finde „tttttttttt“ (‚tttt’+’ttt’+’tt’+’t‘ *). Und somit wäre der Ausdruck t{1,4} der Erklärung nach mit t{10} (bzw. t{10,10}) identisch.
Müsste man zur Erklärung dann nicht eher folgendes sagen:
t{1,4} heißt: Finde „tttt“ oder „ttt“ oder „tt“ oder „t“
*) PHP-Experten dürfen gerne die „+“-Zeichen durch einen Punkt (.) ersetzen ;-)
Uff, Deinen mittleren Absatz kann ich nicht nachvollziehen, vermute aber, dass Du die Validierungsreihenfolge bzw. Erfüllungsreihenfolge eines Quantifiers meinst.
t{1,4} ist nämlich nicht gleich t{10} und auch nicht t{10,}
Der Artikel verschweigt an der Stelle leider die vollständige Funktion des „?“
Ein Fragezeichen hinter einem Quantifier, also einer Mengenangabe von Zeichen lässt die Erfüllung der Bedingung „geizig“ werden. Uff ist das kompliziert. ;-)
Vorausgesetzt man hat die Quantifier verstanden erkläre ich mal so:
String: xtttyyy
xt+ ist identisch mit xt{1,} es findet x gefolgt von mindestens einem t
Er würde xttt matchen.
xt{1} ist identisch mit xt{1}? und macht absolut keinen Sinn.
Es matched xt.
Interssant wird es erst bei:
xt{1,3} bzw. xt{1,3}?
Während der erste die maximale Anzahl an „t“ zu kassieren ist der zweite Ausdruck geizig und gibt sich mit der geringsten Menge zufrieden.
Freebie Multiplier, muss ich gestehen, habe ich nicht verstanden. Für mich gehört das ? ebenfalls in die Kategorie Quantifier.
? = 0 oder 1
+ = 1 oder mehr
* = 0 oder mehr
und bezieht sich immer auf die vorangegangene Gruppe, Klasse, Zeichen oder Muster.
Der ? hinter einem Quantifier also +? *? oder {1,…}? lässt den Quantifier geizig beim sammeln werden.
Ich habe meine Regulären Ausdrücke gerne immer mit dem Regex Coach geschrieben. M.E. das stabilste und einfachste Werkzeug um nen regulären Ausdruck zu „schmieden“. http://www.weitz.de/regex-coach/
Wieso geht hier niemand auf marvins berechtigten Kommentar ein?
Bitte tauscht das Bild aus, es ist nicht ersichtlich, was es da zu suchen hat!
Liebe Anne. Wir wollen das gar runterspielen, auch wenn für viele hier der Sexismus in dem Artikelbild wohl nicht ersichtlich war. Aber wenn das bei Dir anders ist, tut uns das leid, dann waren wir da wohl nicht sensibel genug. Als Argument zumindest genügt das, das Bild auszutauschen. Viele Grüße aus dem HQ!
Für den Mac gibt es die nette App Patterns: http://krillapps.com/patterns/
Da kann man die RegExs direkt ausprobieren und sieht was sie bewirken.