Reguläre Ausdrücke

Ein regulärer Ausdruck besteht aus einer Folge von normalen Zeichen und Metazeichen. Ein Metazeichen ist ein Zeichen, welches nicht für sich selbst steht, sondern eine besondere Bedeutung erhält. Der einfachste Fall ist ein Metazeichen, das für ein beliebiges Zeichen steht. Bei der Angabe von Dateinamen steht das Fragezeichen ('?') für beliebige Zeichen. Bei der Suche in Texten, beipielsweise mit grep oder dem Suchbefehl in vi oder emacs, ist es der Punkt ('.'). Ein regulärer Ausdruck beschreibt eine Menge von Zeichen, die der beschriebenen Regel folgt. Häufiger Einsatz ist das Suchen oder Suchen und Ersetzen in Texten, aber auch das Auffinden mehrerer Dateien. Wie schon bei . und ? auffällt, unterscheiden sich die Metazeichen je nach Anwendung. Hier werden nur die Fälle betrachtet, die die Shell betreffen und die so genannten Basic-Regular Expressions, die grep, vi, sed u.a. Anwendungen verstehen.

Metazeichen in Dateinamen (Shell Expandierung)

Bedeutung der Metazeichen der Shell:
?Ein beliebiges Zeichen
* Mehrere (auch keins, also 0..n) beliebige Zeichen
[abc] Ein Zeichen aus der Menge abc
[A-Z] Ein Zeichen aus der Menge A...Z
[0-38-9] Ein Zeichen aus der Menge 0,1,2,3,8,9
[!ABC] Ein beliebiges Zeichen, das nicht A,B oder C ist
\ Escape-Zeichen: Das nachfolgende Zeichen wird nicht als Metazeichen interpretiert
"..." Alle Meta-Zeichen innerhalb der Anführungszeichen werden nicht interpretiert

Anmerkung zur letzten Zeile: die Anführungszeichen unterdrücken (maskieren, englischdeutsch: quoten, escapen) auch andere Sonderzeichen, die die Shell sonst interpretiert, wie:

Das $-Zeichen, das eine Variable (z.B. $HOME oder $PATH) einleitet, wird durch die Anführungszeichen nicht maskiert. Einen stärkeren Schutz vor der Interpretation durch die Shell bieten die einfachen Anführungszeichen ('...').

Metazeichen wie ? * [] werden von der Shell interpretiert und eventuell expandiert. Bevor die Shell ein Programm aufruft, werden die regulären Ausdrücke in Ihr expandiert. Dies bezieht sich nur auf Dateinamen bzw. Pfadangaben. Angenommen, in einem Verzeichniss befinden sich die Dateien a, ab, affe und das Verzeichniss ablage, so wird der Aufruf

$ echo a* zunächst ersetzt durch
$ echo a ab affe ablage und danach ausgeführt:
a ab affe ablage
$

Dies erklärt auch, warum ein ls -l a* das Verzeichnis ablage nicht nur aufführt, sondern auch seinen Inhalt ausgibt. Das Verzeichnis ist ein Parameter des ls-Befehls und ls <Verzeichnisname> liefert den Inhalt dieses Verzeichnisses.


Metazeichen in Textmustern (vi, grep, sed)

Hier werden nur einige der Metazeichen aufgeführt. Sie werden aber von den meisten Anwendungen, die (Basic-) reguläre Ausdrücke verarbeiten, verstanden. Dieser minimale Satz an Ausdrücken ist schon ausreichend für komplexe Operationen auf Zeichenketten.
Ein kurzer Überblick über die verwendeten Programme:
grep (Abk. f. Global Regular Expression Print) durchsucht einen Text nach dem Suchmuster zeilenweise und gibt die Zeilen aus, für die das Muster einen Treffer gibt.

In vi kann man mit '/' einen regulären Ausdruck suchen, 'n' geht zum nächsten Treffer, 'N' zum vorherigen.
Manipulationsmöglichkeiten hat man mit dem Befehl zum Ersetzen von Mustern:
:x,ys/such/ersetz/option

Beispiel:
:5,$s/Maus/Katze/g
ersetzt von Zeile 5 bis zur letzten Zeile alle Vorkommen von 'Maus' durch 'Katze'. Ohne das 'g' als Option würde pro Zeile nur der erste Treffer ersetzt werden.

Der Stream Editor sed akzeptiert eine ähnliche Syntax wie vi. Eine Angabe des Bereichs ist aber nicht nötig, da sed von der Standardeingabe zeilenweise liest.
Beispiel:
cat text | sed 's/Maus/Katze/g'
Die Datei 'text' wird über eine Pipe an den Stream-Editor weitergegeben, der in jeder Zeile alle Vorkommen von 'Maus' durch 'Katze' ersetzt. Das Ergebnis schreibt sed nach STDOUT, im Allgemeinen also die Konsole.

Metazeichen im Such-Teil
. Ein beliebiges Zeichen
.* Mehrere (auch keins, also 0..n) beliebige Zeichen
w* Mehrere (auch kein) Vorkommen von 'w'
[abc] Ein Zeichen aus der Menge a,b,c
[A-Z] Ein Zeichen aus der Menge A...Z
[0-3g-j] Ein Zeichen aus der Menge 0,1,2,3,g,h,i,j
[^ABC] Ein beliebiges Zeichen, das nicht A,B oder C ist
^ Zeilen-Anfang (aber: innerhalb eckiger Klammern: Negation s.o.)
$ Zeilen-Ende
\ Escape-Zeichen. Das nachfolgende Metazeichen wird nicht interpretiert
\(  \) Metaklammern. Im Ersetzungsteil kann auf diesen Teil-Treffer zurückgegriffen werden
Metazeichen im Ersetzen-Teil
& Der im Suchteil gefundene Ausdruck
\1 \2 ... Der im Suchteil in Metaklammer 1,2 ... gefundene Teilausdruck

Beispiele

Die Datei 'studenten' enthalte folgenden Text. Zu beachten ist, dass jede Zeile einem bestimmten Muster folgt: Nachname ;Vorname ;Mat.-Nr. ; Datum;. Andere Regelmäßigkeiten sind in der Matrikelnummer (8 aufeinanderfolgende Zahl-Zeichen) und im Datum (drei Zahl-Zeichen-Paare durch Punkt getrennt) zu finden. Diese Regelmäßigkeiten kann man sich bei der Anwendung von regulären Ausdrücken zunutze machen.

Panther ;Paulchen ;67676767;16.10.02;
Erhardt ;Heinz ;30010802;23.10.02;
Kunkel ;Palma ;12812812;30.10.02;
Rambo ;John ;66666666;16.10.02;
Nikuta ;Marie Luise ;12123321;30.10.02;
Erna ;Tante ;96352100;23.10.02;


. und \

Um alle die Zeilen herauszufiltern, die das Datum 30.10.02 enthalten, wird versucht:
cat studenten | grep '30.10.02'
Zusätzlich zur erwarteten Ausgabe taucht die zweite Zeile auf. Auch die Matrikelnummer 30010802 passt auf das Suchmuster, denn der Punkt ersetzt jedes Zeichen. Um den Punkt als Zeichen zu benutzen, muss er maskiert werden:
cat studenten | grep '30\.10\.02'
Nun findet grep nur noch in den Zeilen 3 und 5 einen Treffer und gibt die gesamte Zeile aus (Wichtig: Nicht der Treffer, sondern die gesamte Zeile wird ausgegeben)

Alle Zeichenketten, die aus einem beliebigen Zeichen gefolgt von 'an' bestehen, werden durch die Zeichenkette 'Tun' ersetzen: (Treffer 'Pan' in Zeile 1 und 'Tan' in Zeile 5)
cat studenten | sed 's/.an/Tun/g'


^ und [^ ]

Alle Zeilen, die ein P (großes P) enthalten (Zeile 1 und 3):
cat studenten | grep 'P'
Alle Zeilen, die mit einem P beginnen (Zeile 1):
cat studenten | grep '^P'
Alle Zeilen, die nicht mit einem P beginnen (Zeile 2..5):
cat studenten | grep '^[^P]'
Alle Zeilen, die mit mit A..O und Q..Z beginnen (Zeile 2..5)
cat studenten | grep '^[A-OQ-Z]'
Alle Zeilen, die ein 'Pa' enthalten, auf die aber kein 'u' oder 'n' folgt (Zeile 3):
cat studenten | grep 'Pa[^un]'


.*

Dieser Versuch, alle Zeichen vom Zeilenanfang bis zum ersten Semikolon durch ein X zu ersetzen, funktioniert nicht:
cat studenten | sed 's/^.*;/X/g'
Der Reguläre Ausdruck ^.*; findet den größten möglichen Bereich. Es wird nach einer beliebigen Anzahl Zeichen bis zu einem Semikolon gesucht. Dazu gehören auch andere Semikolons. Um alle Zeichen vom Zeilenanfang bis zum ersten Semikolon zu finden, muss nach beliebigen Zeichen bis zu einem Semikolon gesucht werden, die kein Semikolon sind [^;]
cat studenten | sed 's/^[^;]*;/X/g'

Ein weiteres Beispiel verdeutlicht dies: während der erste Aufruf nur 'X' ergibt, erhält man beim zweiten Aufruf 'Xblabla'.
echo blablabla | sed 's/^.*a/X/'
echo blablabla | sed 's/^[^a]*a/X/'

Um die Matrikelnummer durch ein X zu ersetzen, wird ein regulärer Ausdruck gesucht, der alle Vorkommen von 3 oder mehr Zahlen hintereinander findet, da im Datum immer 2 zusammenhängende Zahlen stehen.
cat studenten | sed 's/[0-9][0-9][0-9][0-9]*/X/g'
Die ersten drei eckigen Klammern sorgen dafür, dass mindestens drei aufeinander folgende Zahlen gefunden werden. Die vierte eckige Klammer muss mit dem nachgestelltem Stern zusammen betrachtet werden, da der Stern für 0..n Vorkommen steht, also auch für keine weitere Zahl.

Soll also ein Zeichen, das ein oder mehrmals vorkommt, ersetzt werden, so muss es zunächst einmal aufgeführt werden (grep kennt zwar das Metazeichen '+', das 1..n Vorkommen findet, vi und sed aber nicht):
cat studenten | sed 's/66*/X/g'
Sollen Zeichen, die zwei- oder mehrmals vorkommen, ersetzt werden, so muss das Zeichen zweimal aufgeführt werden, bevor es mit dem Stern 0..n mal gesucht wird:
cat studenten | sed 's/666*/X/g'


&

Um das Gefundene im Ersetz-Teil wieder verwenden zu können, kann es mit '&' addressiert werden.
Allen Zahlen einen Punkt anhängen:
cat studenten | sed 's/[0-9]/&./g'
Alle Großbuchstaben verdoppeln:
cat studenten | sed 's/[A-ZÄÖÜ]/&&/g'
An jede Zeile den Text '1. Semester anhängen' ( ^.*$ findet immer die ganze Zeile) :
cat studenten | sed 's/^.*$/&1.Semester/'

Metaklammern \( \)

Das Datum, das in der Form TT.MM.JJ vorliegt, in die Form MM-TT-JJJJ wandeln. Gesucht werden zunächst die zwei Zahl-Zeichen [0-9][0-9], gefolgt von einem Punkt (\.). Die beiden Zahlen sind in Metaklammern eingeschlossen und stellen den ersten Treffer dar. Die nächsten zwei Zahlen sind ebenfalls in Metaklammern. Die Punkte sind nicht innerhalb der Metaklammern. Im 'Ersetzen'-Teil wird zunächst der zweite Treffer (Monat) angegeben (\2) gefolgt von einem Bindestrich, es folgt der erste Treffer (Tag) gefolgt von Bindestrich und '20' um die Jahreszahl vierstellig darzustellen.
cat studenten | sed 's/\([0-9][0-9]\)\.\([0-9][0-9]\)\./\2-\1-20/'
                      / 1.Metaklammer .  2.Metaklammer./        /

Nur Vorname und Nachname ausgeben und die Felder vertauschen. Die erste Metaklammer schließt alle Zeichen vom Zeilenanfang bis zum ersten Semikolon ein ([^;]*;). Das Semikolon ist jedoch nicht Teil dieser Klammer. Die zweite Metaklammer findet alle Zeichen bis zum zweiten Semikolon. Die dritte Metaklammer findet alle übrigen Zeichen bis zum Zeilenende ($). Im 'Ersetzen' Teil wird dieser dritte Ausdruck nicht verwandt, so dass Matrikelnummer und Datum nicht mehr auftauchen.
cat studenten | sed 's/\(^[^;]*\);\([^;]*\);\(.*\)$/\2\1/'

In C-Quelltexten gibt es mehrere Möglichkeiten einen Kommentar zu kennzeichnen. Kommentare dienen nur des Verständnisses des Quelltexts und werden vom Compiler ignoriert. Kommentare können folgende Form haben:
/* Dies ist ein ANSI (American National Standar Institute) C konformer Kommentar */
// Die ist ein Kommentar, der von vielen C-Compilern akzeptiert wird

Um nun die zweite, nicht ANSI-C konforme, Schreibweise in die erste zu wandeln, bietet sich folgender regulärer Ausdruck an:
echo "// nicht ANSI-C konformer Kommentar" | sed 's/\/\/\(.*\)$/\/*\1 *\//'
                                                     / / (   )   /*   * /
Das Durcheinander kommt durch die vielen Escape-Zeichen zustande: '//' wird zu '\/\/'. '/*' wird zu '\/*' usw.

HTML-Tabelle erstellen
Um eine HTML-Seite mit Tabelle aus der Liste der Studenten zu erstellen, wird ein Skript geschrieben, dass die einleitenden und abschliessenden HTML-Tags erzeugt. Der reguläre Ausdruck ist extrem lang, arbeitet aber nach dem bereits bekannten Prinzip, alle Zeichen zwischen den Semikolons zu finden. Die einzelnen Treffer werden noch mit den HTML-Tags für eine Tabelle umrandet.
HTML-Seiten beginnen mit <html> <body> und enden mit </body> </html>. Eine Tabelle beginnt mit <table> und wird mit </table> abgeschlossen. Zwischen den beiden Tags wird die Tabelle zeilenweise definiert. Man leitet eine Zeile mit <tr> ein, jeder Tabelleneintrag beginnt mit <td>, endet mit </td>. Die Zeile wird mit </tr> beendet. Hier eine kleine 2x2 große Tabelle:

<table border="1">
<tr>   <td> links oben </td> <td> rechts oben </td>   </tr>
<tr>   <td> links unten </td> <td> rechts unten </td>   </tr>
</table>
So sieht es dann aus:
links oben rechts oben
links unten rechts unten
Das folgende Shell-Skript gibt die Liste HTML-Text auf STDOUT aus:

echo '<html><body>'
echo 'Tabelle Studenten PITS'
echo '<table border="1">'
cat studenten | sed 's/^\([^;]*\);\([^;]*\);\([^;]*\);\([^;]*\);/<tr> <td>\4<\/td> <td>\2<\/td> <td>\1<\/td> <td>\3<\/td> <\/tr>/'
echo "</table>"
echo "</body></html>"

Wird das Skript unter dem Namen html.scr abgespeichert und mit chmod +x html.scr ausführbar gemacht, so kann eine Web-Seite mit einer Tabelle erzeugt werden, indem STDOUT des Skriptes in eine Datei umgeleitet wird:
$ ./html.scr > studenten.html

Übungen aus der Vorlesung

1. Punkte durch Kommata ersetzen:
cat studenten | sed 's/\./,/g'

2. Im Datum TT.MM.JJ Monat 10 durch Oktober ersetzen:
cat studenten | sed 's/\.10\./. Oktober /'

3. Skript, dass für jede Gruppe (Abhängig vom Datum) am Anfang der Zeile den Text 'Gruppe 1' (für Datum 16.10.), 'Gruppe 2' (für Datum 23.10.), 'Gruppe 3' (für Datum 30.10.) einfügt.
cat studenten | grep '16\.10' | sed 's/^.*$/Gruppe 1: &/'
cat studenten | grep '23\.10' | sed 's/^.*$/Gruppe 2: &/'
cat studenten | grep '30\.10' | sed 's/^.*$/Gruppe 3: &/'

4. Ersetzen der Matrikelnummer durch ein X (s.o.)
cat studenten | sed 's/[0-9][0-9][0-9][0-9]*/X/g'

5. Nachnamen (von Zeilenanfang bis 1. Semikolon) löschen (s.o.)
cat studenten | sed 's/^[^;]*;//'


Eine Webseite, die sich mit regul�ren Ausdr�cken befasst, aber z.T. wesentlich �ber das hier besprochene Hinausgeht finden Sie hier.
Reguläre Ausdrücke sind kein wildes Durcheinander von Interpunktionszeichen:
Sie sind ein sorgfältig durchdachtes Durcheinander von Interpunktionszeichen! (Perl Kochbuch, S.169)