Tipp: Reguläre Ausdrücke

In dem vorangegangenen Artikel dieser Reihe haben wir mit sed einfache Änderungen an Dateien und Datenströmen vorgenommen. Um den Einsatz von sed effizienter zu gestalten, bedarf es der Kenntnisse über Reguläre Ausdrücke.

Näheres dazu auch in unserem Buch „Linux Essentials“.

Hintergründe

Viele Linux-Kommandos kennen bei Filterfunktionen nicht nur einzelne Sonderzeichen, sondern auch sogenannte Reguläre Ausdrücke (Regular Expressions), auch als RegEx oder RegExp abgekürzt. Ein regulärer Ausdruck ist sozusagen ein Platzhalter, der (durchaus komplexe) Gruppen von Zeichenketten repräsentiert.

Zwar gibt es verschiedene Implementierungen der RegEx, doch sind die meisten kompatibel mit den Perl Compatible Regular Expressions (PCRE). Die Programmier- bzw. Skriptsprache Perl führte die heute bekannten regulären
Ausdrücke in Version 5.0 ein.

Auch das Portable Operating System Interface (POSIX) [POSIX definiert verschiedene Schnittstellen als Standard und vereinfacht damit POSIX-kompatiblen Betriebssystemen die Interaktion.] definiert reguläre Ausdrücke; es ist den PCRE sehr ähnlich, unterscheidet aber etwa einfache und erweiterte RegEx. Welche Art der regulären Ausdrücke und in welchem Umfang sie ein Programm unterstützt, beschreibt dessen Dokumentation.

Reguläre Ausdrücke dienen also allgemein dazu, Texte nach bestimmten, sehr frei definierbaren Mustern zu durchsuchen, um die Ergebnisse weiterverarbeiten zu können.

Grundlagen

Wie in der Shell gibt es bei den regulären Ausdrücken Zeichen, die Sonderfunktionen haben: Der Punkt (.) steht für ein beliebiges Zeichen, der Zirkumflex (^) für den Zeilenanfang oder — innerhalb einer Zeichenklasse — für eine Negierung und das Dollarzeichen ($) für das Zeilenende. Wenden wir diese Ausdrücke auf den folgenden Text (den Schluss von Ernst Jandls Gedicht „ottos mops“) in einer Datei otto.txt an:

user@linux:~$ cat otto.txt
ottos mops kommt
ottos mops kotzt
otto: ogottogott

Zum Einsatz kommt der Befehl grep, der die Ergebnisse einer Mustersuche zeilenweise anzeigt:

user@linux:~$ grep ^ottos otto.txt
ottos mops kommt
ottos mops kotzt

grep liefert zwei Zeilen des Gedichts, da diese mit ottos beginnen.

user@linux:~$ grep tt$ otto.txt
otto: ogottogott

Nur für die letzte Zeile trifft das Muster („zwei t am Zeilenende“) zu.

Darüber hinaus gibt es Notationen für Zeichenklassen,
beispielsweise [:alnum:] für alphanumerische Zeichen, [:blank:] für jegliche Art von Leerzeichen oder [:upper:] für Groß- und [:lower:] für Kleinbuchstaben. Auch die Angabe vonBereichen ist möglich: [a-f]  (die Kleinbuchstaben von a bisf), [0-9] (sämtliche Ziffern) und vieles mehr. Das folgende Kommando sucht die Zeilen, in denen der Buchstabe o, gefolgt von den Buchstaben zwischen i und m, vorkommt.

user@linux:~$ grep  o[i-m] otto.txt
ottos mops kommt

Darüber hinaus gibt es die folgenden vordefinierten Zeichenklassen:

\d
eine Ziffer, also [0-9]

\D
ein Zeichen, das keine Ziffer ist, also [^\d]

\w
ein Buchstabe, eine Ziffer oder der Unterstrich, also [a-zA-Z_0-9] und eventuell weitere Buchstaben, zum Beispiel Umlaute

\W
ein Zeichen, das weder Buchstabe noch Zahl noch Unterstrich ist, also [^\w]

\s
Leerzeichen und die Klasse der Steuerzeichen \f (Seitenvorschub), \n (Zeilenumbruch), \r (Wagenrücklauf), \t (Tabulator) und \v (vertikaler Tabulator)

\S
ein Zeichen, das kein Leerraum (Whitespace) ist, also [^\s]

Alle Kombinationen von regulären Ausdrücke basieren auf prinzipiell drei Operationen: Alternative, Verkettung und Wiederholung.

Ein vertikaler Strich (das Pipezeichen |) markiert eine Alternative. Aber auch Zeichenklassen, eingeschlossen in eckige Klammern ([ und ]), weisen Alternativen aus. Mit einer Start- und Endziffer oder Buchstaben mit einem Minus (-) dazwischen lassen sich innerhalb eckiger Klammern Bereiche definieren.

Auch Wiederholungen kann der Benutzer sehr fein definieren. Ein Fragezeichen (?) steht für einen Ausdruck, der einmal vorhanden sein kann, aber nicht muss. Ein Asterisk (*) steht für „beliebig oft“, was auch die Anzahl null, also
„nicht vorhanden“, einschließt. Ein Plus (+) steht für „mindestens einmal“, kann also ein oder beliebig viele  Vorkommen des Ausdrucks treffen.

Geschweifte Klammern ({ und }) lassen noch genauere Definitionen zu: Eine so eingeschlossene Zahl fordert die Wiederholung entsprechend oft ein. Eine Zahl gefolgt von einem Komma innerhalb der geschweiften Klammern gibt einen Mindestwert für die Anzahl der Wiederholungen an. Zwei Zahlen von einem Komma getrennt in geschweiften Klammern stehen schließlich für einen fest definierten Bereich von Wiederholungen.

Zeichen sind immer miteinander verkettet, das heißt, der Benutzer definiert die gewünschte Reihenfolge. Doch alle anderen Operationen (zum Beispiel die Wiederholung im vorherigen Absatz) haben dabei eine stärkere Bindung. Wer jedoch der Verkettung eine stärkere Bindung zuweisen will, umschließt sie mit runden Klammern ( und ).

Will der Anwender die Zeichen [ ] ( ) { } | ? + -* ^ $ \ . ohne ihre Sonderbedeutung, als druckbare Zeichen, benutzen, muss er sie escapen, also der Umgebung mitteilen „Achtung, jetzt das Zeichen ohne Meta-Bedeutung“. Dies geschieht mit einem vorangestellten Backslash (\).

Spätestens jetzt sollte deutlich geworden sein, wie komplex,
aber eben auch mächtig reguläre Ausdrücke sein können und welche Flexibilität sie dem Benutzer bei der Arbeit bieten; nicht zufällig füllt das Thema ganze Bücher. Was auf den ersten, unerfahrenen Blick überladen scheint, erweist sich in der Praxis sehr schnell als überaus hilfreich oder gar notwendig.

Stellen Sie sich einen Administrator vor, der für die Mailserver eines Internet-Providers mit Tausenden E-Mail-Adressen zuständig ist. In den Wochen zuvor gab es eine Flut von Spam-Mails, und die Aufgabe lautet nun: „Suche in den Log-Files der vergangenen zwei Wochen alle Nachrichten, die im Betreff bestimmte Wörter enthalten, deren Absenderadresse von einer bestimmten Top-Level-Domain kommt und die über Server aus einem bestimmten Netzbereich verschickt wurden.“

Beispiele

Aufgaben dieser Art sind — das sollte deutlich geworden sein — ohne Filter nicht zu lösen. Das Potential aus der Verbindung von Linux-Kommandos, Shell und regulären Ausdrücken lässt sich hier nur andeuten und soll vor allem zu weiteren Recherchen anregen.
Hier noch einige Beispiele für funktionierende reguläre Ausdrücke, sozusagen als „Appetitanreger“:

abc
die Zeichenkette „abc“

(abc|xzy)
die Zeichenkette „abc“ oder „xyz“

[abcx-z0-9]
eines der Zeichen „a“, „b“, „c“, „x“, „y“, „z“ oder eine Ziffer

^.$
genau ein beliebiges Zeichen in der Zeile

^#
die Zeile beginnt mit einer Raute (derartige Formatierungen sind häufig als Kommentare in Shellskripten zu finden)

\w+\.[a-zA-Z0-9]{2,}
ein oder mehr Buchstaben, Ziffern oder Unterstriche, dann ein Punkt, gefolgt von mindestens zwei Buchstaben und/oder Ziffern

eine IPv4-Adresse
^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$

eine gültige E-Mail-Adresse
^((?:(?:(?:\w[\.\-\+]?)*)\w)+)\@((?:(?:(?:\w[\.\-\+]?){0,62})\w)+)\.(\w{2,6})$

sed mit Regulären Ausdrücken

Nachdem wir nun die Regulären Ausdrücke und deren Möglichkeiten kennengelernt haben, wenden wir sie im nächsten Teil dieser Artikelserie an.