Reguläre Ausdrücke - praktischer Kurs

Aus DebianforumWiki
Zur Navigation springen Zur Suche springen
Wiki ‹ Scripting ‹ Reguläre Ausdrücke - praktischer Kurs


Wiki ‹ Softwareentwicklung und paketierung ‹ Reguläre Ausdrücke - praktischer Kurs


Diskussion 183941

Übersicht: Reguläre Ausdrücke

Teil 01

Diskussion 183942 Diskussion 183974

Motivation

Wozu braucht man überhaupt REs?

Einfach gesagt kann man mit REs Text suchen. Was ihr alle kennt ist eine Stringsuche, d.h. eine ganz normale Textsuchfunktion: Man gibt ein Wort ein und sucht dieses im Text. Leider ist diese Suchmöglichkeit etwas begrenzt in ihrer Fähigkeit.

Durchsuchen wir doch mal Text nach ‚Samstag‘. Anschliessend sollten wir wohl eine zweite Suche starten, um den gleichen Text auch nach ‚Sonnabend‘ zu durchsuchen. Es wäre schöner wenn wir beides mit einer Suche erledigen könnten. REs können das. In dem Fall ist es nur eine kleine Arbeitsersparnis.

Wie war das nochmal mit dem Namen Maier? Schreibt man den mit ‚ai‘ oder ‚ei‘ oder mit ‚ay‘ oder ‚ey‘ oder nur mit ‚y‘? Hier sind es schon fünf verschiedene Suchen, die wir ausführen müssen. Mit REs nur eine.

Nun suchen wir nach dem Ortsnamen ‚Au‘. Das ist zwar nur eine Suche, aber sie liefert uns leider auch ‚Auge‘, ‚Auto‘, usw. Wir wollen aber nur Treffer bei denen ‚Au‘ ein eigenständiges Wort ist. Mit REs ist das einfach. Mit Stringsuchen geht es nur falls das jeweilige Programm es zufällig anbietet.

In einem Logfile interessieren uns alle Zeilen, die mit ‚incoming‘ beginnen. Wenn ‚incoming‘ irgendwo sonst innerhalb der Zeile vorkommt interessiert es uns nicht. Eine solche Unterscheidung ist mit einer Stringsuche nicht möglich.

Bis hierhin kann man mit Stringsuchen die Suchprobleme auch lösen, wenngleich mitunter mehrere Suchen nötig sind oder das Suchresultat auch unerwünschte Treffer enthält.

Die folgenden Suchprobleme können mit Stringsuchen nicht gelöst werden, mit REs aber sehr wohl.

  • Negativ-Suchen: Alle Zeilen in denen das Wort ‚debug‘ nicht enthalten ist. Stringsuchen können nur positiv suchen, also all die Stellen finden, wo das Suchwort steht, aber sie können nicht die Stellen finden wo es nicht steht.
  • Zahlen: Es sollen alle numerischen Werte gesucht werden ... oder noch genauer: alle dreistelligen Zahlenwerte -- unmöglich mit einer Stringsuche.
  • Komplexe Formate, wie Datumsformate und überhaupt alles was variable Anteile hat.

REs können all diese Suchprobleme mit Leichtigkeit lösen.

Aufgaben

  1. Welche Suchprobleme hattest du schon, die du entweder nicht lösen konntest oder für die du viel Handarbeit investieren musstest?
  2. Kennst du Programme, deren Stringsuche (ohne REs) mehr kann als nur stupide nach einem Suchwort zu suchen? Oft kann man z.B. die Unterscheidung von Gross-/Kleinschreibung an- und abschalten. Was ist dir da schon begegnet?
  3. An was für Anwendungsfälle denkst du wenn du „Reguläre Ausdrücke“ hörst?

Teil 02

Diskussion 183974

Metazeichen & Escaping

Diese Einheit ist die konzeptionelle Basis zum Lesen von REs.

Eine herkömmliche Stringsuche ist genau genommen eine Substringsuche, d.h. wir suchen den Substring (= Suchwort) im Gesamtstring des Textes. Sowohl der Gesamttext als auch das Suchwort können dabei jedes beliebige Zeichen enthalten. Das Suchwort besteht zudem aus nichts anderem als einer Reihe von Zeichen, die in der Reihenfolge im Text gesucht werden sollen.

Reguläre Ausdrücke bieten darüber hinaus weitere Möglichkeiten. Man kann in ihnen Zusatzinformationen hinterlegen, wie dass es entweder das eine oder das andere Wort ist (Samstag/Sonnabend) oder dass es der eine oder der andere Buchstabe ist (Maier/Meier/Mayer/...). Oder dass ein Wort am Zeilenanfang stehen muss oder dass die Buchstaben ein abgeschlossenes Wort bilden müssen.

Die Herausforderung ist nun, dass ein Regulärer Ausdruck sowohl die gesuchten Buchstaben als auch diese Zusatzinformationen enthält.

Das ist wichtig zu verstehen! Während bei einer Stringsuche das Suchwort nur gesuchte Zeichen enthält, enthält eine RE sowohl gesuchte Zeichen als auch Zusatzinformationen zur Suche.

Begrifflich nennt man die gesuchten Zeichen „literale Zeichen“; sie sollen genau so im Text gesucht werden. Die Zusatzinformationen nennt man „Metazeichen“; sie tragen Informationen bei, die so nicht wörtlich im Text zu finden sind.


Falls man bei einer Stringsuche die Gross-/Kleinschreibungsunterscheidung aktivieren und deaktivieren kann, so ist diese Option auch eine Metainformation. Jedoch wird diese nicht innerhalb des Suchwortes umgesetzt, sondern als Checkbox oder Schalter ausserhalb des Suchfelds. In diesem Falle ist die Trennung zwischen literalen Zeichen und Metainformation einfach: sie ist strukturell im User Interface gegeben.

Bei REs dagegen sind beide Informationen in einem einzigen Ausdruck (also einem Wort, einer Zeichenfolge) vermischt. Einerseits macht es das beqüm, da man nur einen einzigen RE-String eingeben muss, der schon alles enthält, und nicht in jedem Programm weitere User-Interface-Elemente zur Angabe der weiteren Optionen braucht, andererseits macht gerade diese Vermischung REs so schwer verständlich.

Unsere Hauptaufgabe beim Lesen von REs wird folglich sein, im Ausdruck Literale und Metazeichen zu unterscheiden.


Wie kann man zwei Arten von Zeichen in einem Ausdruck unterscheiden?

Im einfachsten geht das wenn die zwei Arten von Zeichen disjunkt sind, d.h. ohne überschneidung. In dieser Weise nutzen Mathematiker und Physiker gerne griechische Buchstaben für ihre Variablen. Es ist dann sofort klar, ob etwas eine Variable oder normaler Text ist.

Eine andere Art der Unterscheidung kann durch das Formatieren der Zeichen erreicht werden. Wenn man Fett- und Kursivschrift oder Farben zur Verfügung hat, kann man damit die sonst gleichen Zeichen unterscheidbar machen.

Bei Regulären Ausdrücken haben wir leider keine dieser beiden Möglichkeiten zur Verfügung. Zum einen können alle Zeichen im Text und damit literal im Regulären Ausdruck vorkommen, so dass wir keine weitere Zeichenmenge für die Metazeichen übrig haben, zum anderen sind die REs selbst reiner Text und können demnach nicht typographisch formatiert werden. Wir brauchen folglich einen anderen Mechanismus, um die beiden Zeichenarten zu trennen.

Der in solchen Fällen genutzte Mechanismus heisst Escapen.


Escapen funktioniert folgendermassen:

  1. Wir wählen ein Escapezeichen. (Man nimmt möglichst ein Zeichen, das selten im Text vorkommt, aber prinzipiell ist jedes Zeichen möglich.)
  2. Entweder die Literale oder die Metazeichen sind die Standardzeichen. Die andere Zeichenart muss escapet werden. D.h. vor jedes Zeichen der anderen Zeichenart muss ein Escapezeichen kommen.
  3. Um das Escapezeichen literal zu erhalten muss es (mit sich selbst) escapet werden. (Zwei aufeinander folgende Escapezeichen stehen immer für ein literales Escapezeichen.)


Nun haben wir einen Mechanismus mit dem wir jedes Zeichen in zwei Arten interpretieren können: In der ersten Art wenn es nach einem Escapezeichen kommt und in der zweiten Art wenn es nicht nach deinem Escapezeichen kommt.

Wir können das Escapezeichen dabei verstehen als einen Hinweis: Achtung, das nächste Zeichen gehört zur anderen Zeichenart. (Wikipedia: „an escape character is a character that invokes an alternative interpretation on the following character“.)


Die Unterscheidung und Identifizierung von Literalen und Metazeichen in einem Regulären Ausdruck ist von zentraler Bedeutung. Sie ist die Basis des Verständnisses von REs. Dazu muss das Konzept des Escapens gut verstanden sein.

Hier folgen nun verschiedene Übungen, um mit diesem Konzept warm zu werden. Am besten, ihr arbeitet mit Bleistift, Papier und Farben. Bei den letzten Aufgaben könnt ihr dann auch selber programmieren (wenn ihr das könnt) und eure vorigen Lösungen damit nochmal abgleichen.

Aufgaben

  1. Das Escapezeichen ist der Unterstrich (_). Literale Zeichen sind die Standardzeichen. Schreibe den Ausdruck für das literale Wort: „Haus“.
  2. Das Escapezeichen ist der Unterstrich (_). Metazeichen sind die Standardzeichen. Schreibe den Ausdruck für das literale Wort: „Haus“.
  3. Das Escapezeichen ist das grosse X. Metazeichen sind die Standardzeichen. Schreibe den Ausdruck für das literale Wort: „Haus“.
  4. Das Escapezeichen ist das kleine a. Literale Zeichen sind die Standardzeichen. Schreibe den Ausdruck für das literale Wort: „Haus“.
  5. Suche dir ein Escapezeichen aus. Literale Zeichen sind die Standardzeichen. Schreibe die Zeichenfolge: Literales ‚D‘, literales ‚F‘, literales ‚D‘, literales ‚E‘, Metazeichen ‚J‘, literales ‚R‘, literales ‚E‘, literales ‚s‘, literales ‚!‘, Metazeichen ‚J‘, Metazeichen ‚@‘.
  6. Schreibe den gleichen Text aus (5) aber mit Metazeichen als Standardzeichen.
  7. Das Escapezeichen ist die öffnende runde Klammer (‚(‘). Suche dir aus, welche Zeichenart die Standardzeichen sind. Schreibe einen Ausdruck mit nur öffnenden und schliessenden runden Klammern und erkläre ihn anschliessend.
  8. Das Escapezeichen ist das Komma (,). Literale Zeichen sind die Standardzeichen. Beschreibe den Ausdruck (vgl. (5)): „A,BCD,E,FG,,H“
  9. Das Escapezeichen ist das Komma (,). Metazeichen sind die Standardzeichen. Beschreibe den Ausdruck: „hello, world“
  10. Das Escapezeichen ist das kleine a. Literale Zeichen sind die Standardzeichen. Beschreibe den Ausdruck: „Haus“
  11. Das Escapezeichen ist das kleine a. Literale Zeichen sind die Standardzeichen. Beschreibe den Ausdruck: „Haaus“
  12. Das Escapezeichen ist das kleine a. Metazeichen sind die Standardzeichen. Beschreibe den Ausdruck: „Haaus“
  13. Schreibe ein Programm, das literalen Text passend escapet. Das Escapezeichen und die Entscheidung, welche Zeichenart die Standardzeichen sind, sollen variabel sein (z.B. CLI-Argumente). Prüfe damit deine Lösungen für die Aufgaben (1) bis (7).
  14. Auf die vorigen Aufgabe aufbauend: Schreibe ein Programm, das den escapeten Text wieder einliest und eine sprachliche Beschreibung davon ausgibt, ähnlich wie in (5). Prüfe damit deine Lösungen für die Aufgaben (8) bis (12).

Teil 03

Diskussion 184043

egrep

Nun geht es an die Praxis. Wir verwenden hierfür nun egrep, das auf POSIX Extended Regular Expressions (EREs) basiert.

Das Escapezeichen ist bei allen üblichen RE-Varianten der Backslash (\).

Was die Literale und Metazeichen angeht, ist es leider nicht so schön klar wie wir es in der vorigen Einheit betrachtet haben. Dort waren jeweils die unescapeten Zeichen Literale und die escapeten Zeichen Metazeichen, bzw. umgekehrt. In der Praxis sind diese zwei Zeichenarten nicht so klar getrennt, sondern vermischt. Es ist weiterhin so, dass das Escapen die Zeichenart ändert, aber sowohl unescapete Zeichen als auch escapete Zeichen können sowohl Literale als auch Metazeichen sein. Das macht die Sache etwas schwieriger. Bei den unescapeten Zeichen sind also manche Literale und manche Metazeichen; welche was sind muss man auswendig lernen. Immerhin sind bei EREs alle escapeten Zeichen Literale. (Das ist bei BREs nicht so.) Allerdings kann man auch wiederum nicht einfach ein beliebiges Zeichen escapen, um es zu einem Literal zu machen. Escapet man beispielsweise einen Buchstaben, so ist das Verhalten undefiniert. Und da POSIX nur eine nachträgliche Zusammenfassung des grössten gemeinsamen Nenners der vielfältigen Unix-Realität ist, verhalten sich die konkreten Programme oft nochmal ein bisschen anders als es in POSIX beschrieben ist.

Konkret heisst das, dass man für jede RE-Variante und letztlich für jedes Programm separat auswendig lernen muss, welche Zeichen Sonderbedeutungen haben!

Das ist wohl die grösste Schwierigkeit bei der Verwendung von REs. Wir gehen vorerst von EREs aus (z.B. in egrep, awk), die in der Hinsicht etwas einfacher sind als BREs (z.B. in grep, sed).


In EREs steht jedes Zeichen für sich selbst, ausser es ist ein Zeichen mit Sonderbedeutung. Die Zeichen mit Sonderbedeutung sind:

\.[]()*+?{}|^$

... wobei sie leider auch nicht an jeder Stelle in einem Ausdruck eine Sonderbedeutung haben. Und Zeichen wie - und , haben nur an bestimmten Stellen eines Ausdrucks eine Sonderbedeutung. Dies ist nunmal so und ich kann es euch leider nicht einfacher machen. :-(

Kurzum können wir uns aber merken, dass alle Buchstaben, Zahlen und z.B. Unterstrich, Apostroph und Anführungszeichen immer literal für sich selbst stehen.

Obige Sonderzeichen sind an den meisten Stellen Metazeichen. Wenn wir sie escapen, werden sie literal interprätiert.

Aufgaben

  1. Schreibe einen egrep-Ausdruck der die Zeichenkette „Reiter“ matcht. Teste ihn entweder so:
    user@debian:~$ echo "Reiter"  |  egrep 'DEIN_AUSDRUCK'
    
    Wenn das Wort „Reiter“ ausgegeben wird, hat der Ausdruck gematcht. Oder wende den egrep-Befehl auf den Text schwäbische-kunde.txt (s.u.) an. Dort werden dann alle Zeilen ausgegeben, die „Reiter“ enthalten. Wenn du `egrep -o' verwendest, dann werden nur die gematchten Worte selbst ausgegeben.
  2. Schreibe einen egrep-Ausdruck der die Zeichenkette „Viel Steine“ matcht.
  3. Schreibe einen egrep-Ausdruck der die Zeichenkette „heil'gen“ matcht.
  4. Schreibe einen egrep-Ausdruck der die Zeichenkette „Er sprach: "Sagt an,“ matcht.
  5. Schreibe einen egrep-Ausdruck der die Zeichenkette „eben.“ matcht. Testet mit ‚-o‘ und ohne.
  6. Schreibe einen egrep-Ausdruck der die Zeichenkette „?“ matcht.
  7. Vergleiche diese egrep-Ausdrücke mit der Verwendung von fgrep, welches eine simple Stringsuche umsetzt. Bei fgrep werden alle Zeichen literal interprätiert. Wo liegen die Unterschiede? (Bis zu diesem Zeitpunkt wird euch egrep noch nicht besser als fgrep vorkommen. Das aendert sich bald.)
  8. Verwende egrep für ein Stück Quellcode, der Sonderzeichen enthält. Schreibe dafür egrep-Ausdrücke, die Sterne, Klammern und Punkte matchen.

Inputtext: schwaebische-kunde.txt Icon pastebin.gif 41651

Teil 04

Diskussion 184095

Nachdem ihr nun mit egrep und dem Escapen der dort geltenden Zeichen mit Sonderbedeutung vertraut seid, kommen wir nun zu den ersten Operatoren. Diese machen egrep mächtiger als fgrep.

Konkatenation

Der einfachste Operator in REs ist die Konkatenation. Auf Deutsch: die Aneinandereihung. Der Operator ist deshalb einfach, weil er implizit ist. Wenn wir zwei Ausdrücke hintereinander schreiben, dann bilden sie damit einen grösseren Ausdruck. Es gibt kein Operator(meta)zeichen, das wir verwenden müssten.

Beispielsweise ist „Schwaben“ ein Ausdruck, den wir mit egrep in der Datei schwaebische-kunde.txt matchen können. Gleichermassen ist „streiche“ ein solcher Ausdruck. Diese beiden können wir auch einfach hintereinanderstellen, um einen konkatenierten Ausdruck zu bilden: „Schwabenstreiche“.

Das mag trivial klingen, trotzdem ist es ganz gut, sich das zu vergegenwärtigen. Im kleinsten Fall ist ein Ausdruck ein einziges Zeichen. So ist der Ausdruck „Schwaben“ ja auch schon eine Konkatenation von acht einzelnen Ausdrücken mit je einem literalen Zeichen.

Eine Stringsuche ist auch nur eine Konkatenation literaler Zeichen. Derart waren all die Aufgaben der vorigen Einheit. Die Möglichkeiten der Stringsuche enden dort; bei uns geht es jetzt erst richtig los! ;-)

Alternation

Der zweite (und erste wirklich interessante) Operator ist die Alternation, die zwei Ausdrücke in eine Oder-Beziehung setzt. Wenn ich also mit einem Ausdruck sowohl „Schwabe“ als auch „Held“ suchen will, dann kann ich die beiden Ausdrücke per Alternation verbinden.

Die Alternation wird durch den Operator Pipe (|) angegeben. Das Pipe-Zeichen ist also ein Sonderzeichen. Tritt es in der RE auf, dann wird es als Operator interpretiert. Will man es literal matchen, dann muss man es escapen. (Achtung: Je nach RE-Variante kann das auch umgekehrt sein.)

Der Reguläre Ausdruck für die erwähnte Suche wäre damit:

Schwabe|Held

Dies ist auch mehrfach möglich:

Spiess|Säbel|Pfeil|Schwerdt

(Hier nochmal der Text vom vorigen Teil, um die Beispiele anzuwenden: TODO Inputtext aus 3)

Unterausdrücke

Um nicht nur eine Ebene von Alternation zu haben, sondern diese verschachteln zu können, brauchen wir eine Möglichkeit, einen komplexen Ausdruck als Einheit zu betrachten. Das geht indem wir ihn in runde Klammern einfassen.

Runde Klammern ( und ) sind also Sonderzeichen. Wenn sie in der RE auftreten, dann werden sie als Metazeichen interpretiert. Will man sie literal matchen, dann muss man sie escapen. (Achtung: Je nach RE-Variante kann das auch umgekehrt sein.)

Wir könnten die Ausdrücke zwar immer auch ausschreiben, aber es ist weniger Schreibarbeit wenn wir gleiche Teile aus der Klammer rausziehen können.

Nehmen wir beispielsweise alle Kombinationen von einem zu- und abnehmenden Mond bzw. Wind. Ausgeschrieben könnte man das folgendermassen zu einem Regulären Ausdruck zusammenbauen:

zunehmender Mond|abnehmender Mond|zunehmender Wind|abnehmender Wind

Das ist eine ganze Menge Text, in der sich viel wiederholt. Mit Unterausdrücken geht es auch kompakter:

(zu|ab)nehmender (Wind|Mond)

Hier gilt die Alternation jeweils nur auf die geklammerten Teile.

Man kann Alternationen auch Schachteln:

Erd(apfel|birne)|Kartoffel

Generell kann man jeden vollständigen Regulären Ausdruck in runde Klammern fassen und dann als Unterausdruck in einem anderen Ausdruck verwenden.

Rekapitulation und eine Bemerkung

Hier ein etwas angepasster und mit Anmerkungen versehenen Ausschnitt aus der Manpage regex(7), der unseren Wissensstand bis zum aktuellen Punkt zusammenfasst:


man 7 regex
       A Regular Expression is one or more branches, separated by '|'.
       It matches anything that matches one of the branches.
       [= Alternation]
       A branch is one or more pieces, concatenated. It matches a match
       for the first, followed by a match for the second, etc.
       [= Konkatenation]
       A piece is an atom [...].
       An atom is a regular expression enclosed in "()" (matching a
       match for the regular expression) [= Unterausdruck],
       [...],
       a '\' followed by one of the characters "^.[$()|*+?{\"
       (matching that character taken as an ordinary character)
       [= escapetes Metazeichen das dann literal interpretiert wird],
       [...],
       or a single character
       with no other significance (matching that character)
       [= Literal].
       [...]

(Die Lücken in dieser Beschreibung werden wir in den nächsten Einheiten nach und nach füllen.)

In der gleichen Manpage steht auch folgender Abschnitt, der zwar auf die regex-Lib von Henry Spencer zutrifft aber leider nicht generell auf POSIX oder sonstige RE-Varianten:


man 7 regex
       An atom is [...]
       a '\' followed by one of the characters "^.[$()|*+?{\"
       (matching that character taken as an ordinary character),
       a '\' followed by any other character (matching that
       character taken as an ordinary character, as if the '\'
       had not been present)


Der erste Satz gilt generell für alle EREs. Der zweite leider nicht. Wäre es anders, dann hätte ich anfangs die Dinge nicht so kompliziert machen müssen, denn dann hätte ich sagen koennen:

  • Das Escapezeichen ist der Backslash.
  • Metazeichen sind die Standardzeichen.
  • Jedes escapete Zeichen steht literal für sich selbst.

Das wäre klar und konsistent gewesen. Damit wären die beiden Zeichenklassen (Literale und Metazeichen) klar getrennt gewesen.

Der Bequemlichkeit halber kann man sich mit folgender Zusatzregel dann noch Tipparbeit sparen:

  • Metazeichen, die keine Sonderbedeutung haben, stehen automatisch als Fallback auch literal für sich selbst. (‚a‘ und ‚\a‘ sind also beide ein literales ‚a‘.)

Leider gilt das aber eben nur für Programme, die Henry Spencers regex-Lib verwenden. Für andere RE-Implementierungen ist die Situation etwas komplizierter, wenn man sie konzeptionell beschreiben will, da es implementierungsabhängig ist, was ‚\a‘ bedeutet. In der Bedienpraxis macht das aber selten einen relevanten Unterschied.

Aufgaben

  1. Schreibe einen egrep-Ausdruck, der sowohl eine Alternation enthält als auch das Pipezeichen literal matcht und wende ihn auf einen dazu passenden Eingabetext an.
  2. Finde ein inhaltlich sinnvolles Beispiel für eine zwei- oder dreifach verschachtelte Alternation. ;-)
  3. Schreibe einen egrep-Ausdruck (nur mit Alternation und Unterausdrücken), um die Schreibweisen Maier, Meier, Mayer, Meier zu matchen.
  4. Ergänze (3) um die Schreibeweise Myer.
  5. Finde alternative Ausdrücke für (3) und (4) (ohne andere RE-Operatoren zu verwenden, sondern nur indem du anders gruppierst).
  6. Versuche diese Aufgaben auch mit fgrep umzusetzen. Was sind deine Erkenntnisse?
  7. Braucht man runde Klammern um den gesamten Ausdruck wenn man eine Alternation (auf oberster Ebene) verwendet?

Teil 05

Diskussion 184152

Zeichenklassen

Zeichenklassen kann man sich so ähnlich wie Alternationen vorstellen, bloss für einzelne Zeichen statt für ganze Ausdrücke. Von der Ausdrucksmöglichkeit sind sie theoretisch nicht unbedingt nötig wenn man Alternation und Unterausdrücke hat, aber in der Praxis sind sie ungeheuer praktisch. Ausserdem sind sie ein Standardfeature, das man in in allen RE-Varianten identisch wiederfindet.

Wenn wir nach „Samstag“ oder „Sonnabend“ suchen wollen, dann brauchen wir dafür eine Alternation: ‚Samstag|Sonnabend‘.

Wenn wir nach „Maier“ oder „Meier“ suchen wollten, dann können wir das ebenfalls mit einer Alternation machen: ‚Maier|Meier‘. Hier ist die Abweichung der zwei Varianten jedoch nur gering. Den gemeinsamen Teil können wir darum auch nur einmal schreiben und einen Unterausdruck nutzen: ‚M(a|e)ier‘.

Wenn nun alle Teilausdrücke der Alternation nur genau ein Zeichen sind, dann können wir stattdessen auch eine Zeichenklasse verwenden:

M[ae]ier

Eine Zeichenklasse steht immer für genau ein einziges Zeichen. Dieses kann ein beliebiges der aufgelisteten Zeichen sein.

Eine Zeichenklasse wird mit eckigen Klammern geschrieben, zwischen denen alle Zeichen aufgeführt werden für die die Zeichenklasse stehen kann. In diesem Beispiel kann die Zeichenklasse also entweder für ‚a‘ oder für ‚e‘ stehen.

Metazeichen

Innerhalb von Zeichenklassen werden fast alle Zeichen, also auch die normalen Metazeichen (wie Pipe und Klammern und auch das Escapezeichen Backslash!), literal interpretiert.

Speziell sind in Zeichenklassen nur wenige Zeichen, auf die ich nun nach und nach komme.

Die schliessende eckige Klammer (]). Diese markiert das Ende der Zeichenklasse. Wenn die schliessende eckige Klammer von der Zeichenklasse literal gematcht werden soll, dann muss sie als erstes Zeichen verwendet werden:

[]abc]

Diese Zeichenklasse steht für eines der vier Zeichen: schliessende eckige Klammer, ‚a‘, ‚b‘ oder ‚c‘.

Die schliessende eckige Klammer hat also an jeder Stelle einer Zeichenklasse eine Metabedeutung (nämlich die Zeichenklasse zu beenden), ausser als erstes Zeichen innerhalb der Zeichenklasse.

Negation

Zeichenklassen können eines was Alternationen nicht können, nämlich den Match invertieren. Eine Zeichenklasse kann man ausdrücken: Jedes Zeichen *ausser* den angegebenen. Dazu muss als erstes Zeichen in der Zeichenklasse ein Circumflex (Caret) stehen. Dieser negiert die Zeichenklasse. Die Zeichenklasse matcht dann alle Zeichen ausser die auf den Circumflex folgenden.

Eine Zeichenklasse, die alle Zeichen, ausser den Satzzeichen Punkt, Ausrufezeichen und Fragezeichen matcht sieht folgendermassen aus:

[^.!?]

Der Circumflex ist also auch ein Zeichen, das innerhalb einer Zeichenklasse ein Metazeichen ist, allerdings nur wenn er als erstes Zeichen in der Zeichenklasse kommt. An jeder anderen Stelle steht er literal für sich selbst.

Diese Zeichenklasse matcht auf die Satzzeichen und den Circumflex:

[.!?^]

Diese ebenfalls:

[.^!?]

Bereiche

Eine Zeichenklasse, die eine beliebige Ziffer matcht kann man noch einigermassen hinschreiben:

[0123456789]

Aber bei einer Zeichenklasse, die einen beliebigen Buchstaben matcht, wird das sehr unpraktisch. Darum gibt es hierfür die Möglichkeit, Bereiche mittels eines Minuszeichens anzugeben:

[a-z]

Da die Reihenfolge der Zeichen vom Zeichensatz abhängt, sollte man dies nur auf US-ASCII anwenden ... genauer gesagt, sind eigentlich nur drei Bereiche (und Teile davon) einigermassen verlässlich:

  1. 0-9 für die Ziffern
  2. A-Z für die Grossbuchstaben
  3. a-z für die Kleinbuchstaben

Wobei bei den Buchstaben nicht unbedingt eindeutig ist, ob Umlaute enthalten sein werden oder nicht. Solange man sich nur im Rahmen von US-ASCII bewegt, klappt das alles gut, darüber hinaus sollte man nicht zu genaue Erwartungen haben. Diese Ungenauigkeiten kann man in der Praxis meist problemlos in Kauf nehmen, angesichts der praktischen Bequemlichkeit von Bereichen in Zeichenklassen.

Will man sowohl Gross- als auch Kleinbuchstaben matchen, so muss man beide Bereiche separat angeben:

[A-Za-z]

Dies steht für ein Zeichen, das ein beliebiger Gross- oder Kleinbuchstabe ist.

Ein Minuszeichen bildet immer dann einen Bereich, wenn es in einer Zeichenklasse zwischen zwei literalen Zeichen steht. In diesen Fällen ist es ein Metazeichen. Steht das Minuszeichen ganz am Anfang oder ganz am Ende, so kann es keinen Bereich aufspannen und steht dann literal für sich selbst.

Vordefinierte Bereiche

Da die Internationalisierung Zeichenklassenbereiche erschwert hat und weil man immer wieder bestimmte Bereiche braucht, die man ggf. auch nicht schnell mal hinschreiben kann (wie alle druckbaren Zeichen, beispielsweise), hat POSIX einige Zeichenklassenausdrücke eingeführt:

[:alnum:] alphanumerische Zeichen
[:alpha:] Buchstaben
[:blank:] Leerzeichen und Tab
[:cntrl:] Steuerzeichen
[:digit:] Ziffern
[:graph:] druckbare Zeichen ausser Whitespace
[:lower:] Kleinbuchstaben
[:print:] alle druckbaren Zeichen
[:punct:] Punktuationszeichen, Klammern, etc.
[:space:] Whitespace
[:upper:] Grossbuchstaben
[:xdigit:] Hexadezimalziffern

TODO schöner formatieren

Jeder davon steht für eine Liste von Zeichen bzw. einen Bereich. So steht, vereinfacht gesagt, ‚[:lower:]‘ für ‚a-z‘. Man achte hier auf die eckigen Klammern! Eine Zeichenklasse, die die Kleinbuchstaben matcht kann so aussehen:

[a-z]

oder so:

[[:lower:]]   

Eine Zeichenklasse, die sowohl Kleinbuchstaben als auch Ziffern matcht kann eine der folgenden Formen haben:

[a-z0-9]
[[:lower:][:digit:]]
[[:lower:]0-9]
[01234[:lower:]5-9]
usw.

Zeichenklassen sind Mengen, d.h. die Reihenfolge ihrer Elemente ist egal.


POSIX beschreibt noch weitere Spezialangaben für innerhalb von Zeichenklassen ([= =] und [. .]), aber die werden seltenst gebraucht und hier darum ausgelassen.

Reihenfolge

Bei der Reihenfolge innerhalb der Zeichenklasse sind die drei Zeichen mit Sonderbedeutung ^ - ] relevant. Der Circumflex hat nur an wirklich allererster Stelle seine Sonderbedeutung. Die schliessende eckige Klammer verliert ihre Sonderbedeutung als erstes Zeichen auch noch hinter dem Circumflex. Wenn also beides auftritt, dann muss der Circumflex zur Negation zuerst kommen. Das Minus verliert seine Sonderbedeutung als letztes Zeichen (wo es auch mit keinem anderen Sonderzeichen kollidieren kann). Oder, falls keine schliessende eckige Klammer vorhanden ist, als erstes Zeichen (ggf. nach einem Negations-Circumflex).

Aufgaben

Schreibe Reguläre Ausdrücke, wenn möglich, dann gerne verschiedene Varianten. Setzte sie mit egrep um und teste sie gegen selbst ausgedachten Input.

  1. RE für eine Hexadezimalziffer.
  2. RE für eine dreistellige Ganzzahl
  3. RE für eine dreistellige Ganzzahl mit (Pflicht-)Vorzeichen.
  4. RE für einen Euro-und-Cent-Betrag kleiner 10,-.
  5. RE für die 31 Tage des Januars (zweistelliges Format mit führender Null). Zwei Varianten.
  6. RE für die 28 Tage des Februars (zweistelliges Format mit führender Null). Zwei Varianten.
  7. RE für ein beliebiges Zeichen, das kein Buchstabe ist.
  8. RE für eine beliebige Ziffer. Drei verschiedene Varianten.
  9. RE für ein Zeichen, das kein Circumflex (^) ist.
  10. RE für eine Zeichenklasse, die nur einen Circumflex matcht.
  11. RE für ein Minus oder eine eckige Klammer.
  12. RE die alles ausser einer schliessenden eckigen Klammer oder einem Circumflex matcht.
  13. Schreibe zwei verschiedene REs, die beide einen Backslash matchen.
  14. Was matcht die Zeichenklasse ‚[A-z]‘ ausser Buchstaben sonst noch? (Zeichensatz: US-ASCII)
  15. RE, die einen Tab matcht. Versuche mehrere Varianten zu finden.
  16. Schreibe einen egrep-Ausdruck (nun auch mit Zeichenklassen), um die Schreibweisen Maier, Meier, Mayer, Meier und Myer zu matchen.
  17. Schreibe eine RE, die den String „8​)“ matcht. ;-) (Das ist eine ernst gemeinte Aufgabe und ist ist mit den bisher gelernten RE-Möglichkeiten umsetzbar. Anmerkung: im Thread hat diese Aufgabe die Nummer 18)

Teil 06

Diskussion 184230

Im Falle von EREs haben wir nun alle Möglichkeiten kennengelernt mit denen man literale Zeichen matchen kann ... mit einer Ausnahmen, die nun folgt:

Punkt

Eigentlich hätte das Metazeichen Punkt thematisch besser zu den Zeichenklassen gepasst, da er nichts anderem entspricht als einer Zeichenklasse, die alle Zeichen enthält. Oder anders gesprochen: der Punkt matcht ein beliebiges Zeichen.

Statt dieser Zeichenklasse (die alle Zeichen enthält):

[[:print:][:cntrl:]]

kann man also auch einfach

.

schreiben. Das ist ungemein praktisch, gerade da REs in der Praxis meist nicht vollkommen exakt sein müssen und der Punkt einem dann eine Menge Tipparbeit spart.


Der vorige Teil mit den Zeichenklassen war allerdings schon sehr lang und der Punkt passt von den Anwendungsszenarien auch gut zu den hier nun vorgestellten Quantoren, da man ihn dort am öftesten antrifft.


Aber rekapitulieren wir erst einmal kurz:

  • Wir können literale Zeichen direkt matchen und sie ggf. escapen, falls es Metazeichen sind.
  • Wir können mit Zeichenklassen eines von mehreren Zeichen bzw. nun mit dem Punkt ein beliebiges Zeichen matchen. Der Punkt und die vordefinierten Zeichenklassenbereiche erlauben es uns auch und unbekannte Zeichen zu matchen.
  • Wir können Alternationen nutzen, um eine von mehreren REs zu matchen. Mittels Klammern geht das auch für Teil-REs.

Was wir noch nicht können ist eine Variabilität in der *Länge* des gematchten Textes. Bislang können wir anhand der RE schon im Voraus sagen wie viele Zeichen die Matches umfassen werden. Durch Alternationen können das verschiedene Längen sein, aber wir kennen sie alle.

Wollen wir aber beispielsweise „beliebig viel Whitespace“ matchen, dann ist uns das bislang noch nicht möglich. Diese Möglichkeit lernen wir nun kennen.


Quantoren

Mit Quantoren kann man angeben wie oft die davor stehende Teil-RE wiederholt vorkommen darf bzw. muss. Es gibt vier Arten von Quantoren in EREs. Sie alle beziehen sich auf die direkt davor stehende RE-Komponente, das kann ein literales Zeichen sein oder eine Zeichenklasse oder ein geklammerter Unterausdruck. Sie beziehen sich also auf die kleinstmögliche Sinneinheit, die vor ihnen steht.


Stern

Der Stern ist der einzige Quantor, der bei allen RE-Varianten verfügbar ist. Er ist stets ein Metazeichen und wird von allen Implementierungen unterstützt.

Ein Stern bedeutet, dass die davorstehende Sinneinheit beliebig oft (null bis unendlich) wiederholt werden kann.

Die RE:

Aa*h!

matcht damit die Worte:

Ah!
Aah!
Aaah!
Aaaah!
Aaaaah!
Aaaaaah!
Aaaaaaah!
Aaaaaaaah!
Aaaaaaaaah!
usw.

Der Stern bezieht sich hier nur auf das ‚a‘, da das die kleinste Sinneinheit ist.


Plus

Oft passender aber nicht in allen RE-Implementierungen verfügbar (oder es muss escapet werden) ist das Plus. Dieses ähnelt dem Stern, steht aber für mindestens ein Vorkommen bis unendlich viele.

Die RE:

A[ua]+!

matcht damit auf:

Au!
Aa!
Auu!
Aua!
Aau!
Aaa!
Aaua!
Auau!
Auuu!
Aaaa!
Aaau!
Aauuauauuauauuauauuauuuuauuuauaaaauuuuuu!
usw.

Sie matcht aber nicht auf:

A!


Fragezeichen

Auch das Fragezeichen ist nicht in allen RE-Implementierungen verfügbar (oder muss escapet werden). Es steht für eine „Wiederholung“ von null- oder einmal und damit eigentlich nicht für eine Wiederholung sondern macht die vorhergehende Teil-RE optional.

So können mit folgender RE:

colou?r

die die britische als auch die amerikanische Schreibweise gemacht werden:

colour
color

Das Fragezeichen kann stets durch eine Alternation mit einem leeren Teil (was jedoch nicht vollständig portabel ist) ersetzt werden:

colo(u|)r


Intervalle

Zuletzt die allgemeinste Form eines Quantors, der alle drei obigen Quantoren ersetzen kann. Intervalle werden mit geschweiften Klammern notiert. (Von wenigen Ausnahmen abgesehen sind Intervalle überall verfügbar. Mit ihnen kann man also auch Plus und Fragezeichen ersetzen, falls es diese in einer RE-Variante nicht gibt. Die geschweiften Klammern müssen in manchen RE-Varianten escapet werden.)

Die geschweiften Klammern können:

  1. ... genau eine Zahl enthalten. Das bedeutet, dass der davorstehende Teil-Ausdruck genau so oft wiederholt werden muss. Diese Form ist nur Syntactic Sugar, der Schreibarbeit spart, denn den Ausdruck:
    [0-9]{3}
    kann man natürlich jederzeit auch so schreiben:
    [0-9][0-9][0-9]
    Hier ein Beispiel mit Unterausdruck für eine Zeile des langen Schlussteils eines bekannten Liedes:
    Na(, na na){4}, hey, Jude 
  2. ... mit einem Komma getrennt, zwei Zahlen enthalten. Diese legen dann die Mindest- und Maximalzahl an Wiederholungen fest:
    (Na(, na na){4}, Hey, Jude){8,24}
  3. ... eine Zahl und ein Komma enthalten. Das bedeutet, dass es keine Obergrenze gibt.
    Wenn die Band also so richtig im Flow und das Publikum gut ist, dann geht auch mit offenem Ende:
    (Na(, na na){4}, Hey, Jude){8,}
    :-D


Gierigkeit

Bei Quantoren (aber auch bei der Alternation) hat die RE-Engine manchmal die Wahl, mehr oder weniger zu matchen. Es ist festgelegt, dass REs immer so viel wie möglich matchen. POSIX nennt das den frühesten längsten Treffer.

Die RE:

a*

wird folglich so viele ‚a‘ wie möglich matchen. Wenn es keine ‚a‘ hat, dann wird sie einen leeren String matchen, aber wenn sie ‚a‘ findet, dann beginnt sie beim ersten, das sie findet, und nimmt ab da so viele wie sie kann.

Aufgaben

  1. Matche mit einer RE allen Input.
  2. Extrahiere mit
    user@debian:~$ egrep -o
    
    alle Zahlen aus einem Text.
  3. Matche in einem XML-Input mit einer RE (ohne Alternation) die Beginn- und Ende-Tags
    <h1>
    und
    </h1>
  4. Schreibe die entsprechenden Intervallausdrücke für die drei anderen Quantoren: * + ?
  5. Erkläre die RE: ‚**‘. Auf was wird sie matchen?
  6. „Bananen?“ -- Dies ist keine Frage, sondern?
  7. Matche alles bis „ENDE“ (inklusive).
  8. Matche den Inhalt eines Single-Quoted Strings (dieser kann das Single-Quote nicht enthalten).
  9. Matche einen Satz, d.h. von einem Grossbuchstaben bis zum nächsten Satzendezeichen. (Zweimal drüber nachdenken und testen. Der erste Ansatz ist nicht unbedingt ausreichend!)
  10. Matche grosse Geldwerte mit Tausendertrenner.
  11. Finde ein sinnhaftes Praxisbeispiel für eine RE mit Punkt aber ohne Quantoren.

Teil 07

Diskussion 184290

Anker

Kommen wir nun zu den letzten zwei Metazeichen von EREs: Anker. Mit ihnen kann man einen Ausdruck am Zeilenanfang bzw. Zeilenende verankern. Anker sind reine Metainformation, sie matchen auf keine Zeichen, sondern sagen nur etwas darüber aus, wo auf der Zeile der Ausdruck gematcht werden kann.

Es gibt zwei Anker in EREs:

Der Zeilenanfangsanker wir mit dem Circumflex (^) notiert. Die RE:

^foo

matcht „foo“ nur wenn es am Zeilenanfang steht.

Der Zeilenendeanker wird mit dem Dollarzeichen ($) notiert. Die RE:

bar$

matcht „bar“ nur wenn es am Zeilenende steht.

Im Falle von EREs ist es erlaubt, diese Anker auch in die Mitte einer RE einzubaün, auch wenn sie dort niemals matchen können. (Das ist bei anderen RE-Varianten anders.)

Damit haben wir nun alle Funktionen von POSIX EREs kennengelernt, lediglich Collation Symbols

([. .])

und Equivalence Class Expressions

([= =])

innerhalb von Zeichenklassen haben wir ausgelassen. Diese habe ich bislang auch noch nie in der Praxis gesehen.

POSIX-Beschreibung von EREs: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04


Präzedenz

Von hoch bis niedrig

  1. Collation-related bracket symbols innerhalb von Zeichenklassen
    [==] [::] [..]
  2. Escapete Metazeichen
    \
  3. Zeichenklassen
    []
  4. Unterausdrücke
    ()
  5. Quantoren
    * + ? {m,n}
  6. Verkettung
    (ohne Operator)
  7. Anker
    ^ $
  8. Alternation
    |

Mit Unterausdrücken (runden Klammern) kann man die Präzedenz anpassen.

Aufgaben

  1. Schreibe eine RE die immer matcht.
  2. Schreibe eine RE die niemals matcht.
  3. Schreibe eine RE die leere Zeilen matcht.
  4. Filtere alle leere Zeilen weg.
  5. Wie kann der Circumflex den Zeilenanfang matchen, wenn er nicht als erstes Zeichen in der RE steht?

Zusammenfassung

Zum Schluss möchte ich nochmal Henry Spencers RE-Beschreibung aus der Manpage regex(7) hervorholen. Anmerkungen blau in eckingen Klammern.


man 7 regex
       Regular  expressions ("RE"s), as defined in POSIX.2, come
       in two forms: modern REs (roughly those of egrep; POSIX.2
       calls  these  "extended"  REs)  and [...BREs...].
       POSIX.2 leaves
       some aspects of RE syntax and semantics open; "(!)" marks
       decisions on these aspects that may not be fully portable
       to other POSIX.2 implementations.
       A  (modern)  RE  is  one(!) or more nonempty(!) branches,
       separated by '|'.  It matches anything that  matches  one
       of the branches.
       [= Alternation]
       A  branch  is  one(!)  or  more pieces, concatenated.  It
       matches a match for the first, followed by  a  match  for
       the second, etc.
       [= Verkettung]
       A  piece is an atom possibly followed by a single(!) '*',
       '+', '?', or bound.  An atom followed by  '*'  matches  a
       sequence  of 0 or more matches of the atom.  An atom fol‐
       lowed by '+' matches a sequence of 1 or more  matches  of
       the  atom.  An atom followed by '?' matches a sequence of
       0 or 1 matches of the atom.
       [= Quantoren]
       A bound is '{' followed by an unsigned  decimal  integer,
       possibly  followed  by  ','  possibly followed by another
       unsigned decimal integer, always followed  by  '}'.   The
       integers  must  lie  between  0  and  RE_DUP_MAX (255(!))
       inclusive, and if there are two of them,  the  first  may
       not  exceed the second.  An atom followed by a bound con‐
       taining one integer i and no comma matches a sequence  of
       exactly  i  matches  of  the atom.  An atom followed by a
       bound containing one integer i  and  a  comma  matches  a
       sequence  of i or more matches of the atom.  An atom fol‐
       lowed by a bound containing two integers i and j  matches
       a  sequence  of  i  through  j (inclusive) matches of the
       atom.
       [= Quantoren]
       An atom is a regular expression enclosed in "()"  (match‐
       ing  a match for the regular expression), an empty set of
       "()" (matching the null string)(!),
       [= Unterausdruck]
       a bracket  expression (see  below),
       [= Zeichenklasse]
       '.'  (matching  any single character),
       [= Punkt]
       '^' (matching the null string at the beginning of a line),
       '$'  (matching  the  null string at the end of a line),  
       [= Anker]
       a '\' followed by  one  of  the  characters  "^.[$()|*+?{\"
       (matching that character taken as an ordinary character),
       a '\' followed by any other character(!)  (matching  that
       character  taken  as an ordinary character, as if the '\'
       had not been present(!)),
       [= escapetes Metazeichen]
       or a single character  with  no
       other significance (matching that character).
       [= literales Zeichen]
       A '{' fol‐
       lowed by a character other than a digit  is  an  ordinary
       character,  not the beginning of a bound(!).  It is ille‐
       gal to end an RE with '\'.
       A bracket expression is a list of characters enclosed  in
       "[]".   It normally matches any single character from the
       list (but see below).  If the list begins  with  '^',  it
       matches any single character (but see below) not from the
       rest of the list.  If two characters in the list are sep‐
       arated  by  '-',  this is shorthand for the full range of
       characters between those two (inclusive) in the collating
       sequence, for example, "[0-9]" in ASCII matches any deci‐
       mal digit.  It is illegal(!) for two ranges to  share  an
       endpoint,  for example, "a-c-e".  Ranges are very collat‐
       ing-sequence-dependent,  and  portable  programs   should
       avoid relying on them.
       [= Zeichenklasse]
       To  include  a literal ']' in the list, make it the first  
       character (following a possible '^').  To include a  lit‐
       eral  '-',  make  it  the first or last character, or the
       second endpoint of a range.  To use a literal '-' as  the
       first  endpoint  of  a range, enclose it in "[." and ".]"
       to make it a collating element  (see  below).   With  the
       exception  of  these and some combinations using '[' (see
       next paragraphs), all other special characters, including
       '\',  lose  their  special  significance within a bracket
       expression.
       [= Zeichenklasse]
       Within a bracket expression, a collating element (a char‐
       acter,  a  multicharacter sequence that collates as if it
       were a single character, or a collating-sequence name for
       either) enclosed in "[." and ".]" stands for the sequence
       of characters of that collating element.  The sequence is
       a  single  element  of  the bracket expression's list.  A
       bracket expression containing a multicharacter  collating
       element can thus match more than one character, for exam‐
       ple, if the collating sequence includes a "ch"  collating
       element,  then the RE "[[.ch.]]*c" matches the first five
       characters of "chchcc".
       [= Collating Symbol (weggelassen)]
       Within a bracket expression, a collating element enclosed
       in  "[="  and  "=]" is an equivalence class, standing for
       the sequences of characters  of  all  collating  elements
       equivalent  to that one, including itself.  (If there are  
       no other equivalent collating elements, the treatment  is
       as  if the enclosing delimiters were "[." and ".]".)  For
       example, if o and ^ are the  members  of  an  equivalence
       class, then "[[=o=]]", "[[=^=]]", and "[o^]" are all syn‐
       onymous.  An equivalence class may not(!) be an  endpoint
       of a range.
       [= Equivalence Class Expression (weggelassen)]
       Within  a  bracket  expression,  the  name of a character
       class enclosed in "[:" and ":]" stands for  the  list  of
       all characters belonging to that class.  Standard charac‐
       ter class names are:
             alnum       digit       punct
             alpha       graph       space
             blank       lower       upper
             cntrl       print       xdigit
       These  stand  for  the  character  classes   defined   in
       wctype(3).   A  locale  may  provide others.  A character
       class may not be used as an endpoint of a range.
       [= Zeichenklassenausdrücke]
       In the event that an RE could match more  than  one  sub‐
       string of a given string, the RE matches the one starting
       earliest in the string.  If the RE could match more  than
       one  substring  starting  at  that  point, it matches the
       longest.  Subexpressions also match the longest  possible  
       substrings,  subject  to  the  constraint  that the whole
       match be as long as possible, with subexpressions  start‐
       ing  earlier in the RE taking priority over ones starting
       later.  Note that higher-level subexpressions  thus  take
       priority over their lower-level component subexpressions.
       [= Gierigkeit; Der längste der frühesten Treffer]
       Match  lengths  are measured in characters, not collating
       elements.  A null string is  considered  longer  than  no
       match  at all.  For example, "bb*" matches the three mid‐
       dle characters of  "abbbc",  "(wee|week)(knights|nights)"
       matches all ten characters of "weeknights", when "(.*).*"
       is matched against "abc" the parenthesized  subexpression
       matches all three characters, and when "(a*)*" is matched
       against "bc" both the whole RE and the parenthesized sub‐
       expression match the null string.  
       [= Gierigkeit; Der längste der frühesten Treffer]

Möglicherweise weitere erwartete Features (wie beispielsweise die Wortgrenzenanker ‚\< \>‘) sind nicht Teil von EREs (was schon daran ersichtlich ist, dass bei EREs alle Metazeichen unescapet sind ;-) ). Dass sie dennoch bei egrep funktionieren, liegt daran, dass egrep nicht strikt POSIX entspricht, sondern verschiedene Erweiterungen und Abweichungen hat. Auf solche und weitere Features werden wir in späteren Kapiteln dieses Kurses noch eingehen. Das Kapitel EREs wird hiermit erstmal geschlossen.