Betrachten wir Variablennamen in typischen Hochsprachen wie Pascal und C. Es handelt sich dabei gewöhnlich um Zeichenfolgen, die aus einem Buchstaben oder Underscore (_) bestehen, dem beliebig viele Buchstaben, Ziffern und Underscores folgen können. Das ist zwar weder aufregend noch neu, dafür aber eine konkrete Anwendung eines, wenn auch nur verbal formulierten regulären Ausdrucks zur Syntaxspezifikation. Die Menge der Zeichenketten, die diesem einfachen Muster entsprechen, ist unendlich, so dass sie nicht durch die Aufzählung aller ihrer Elemente angegeben werden kann. Unter Verwendung des genannten Musters ist die Lösung unseres Problem allerdings mehr als trivial.
Vielleicht lässt sich hier schon die Mächtigkeit des Konzepts erahnen. Um nun die ihm innewohnenden Potenzen nutzen zu können, ist natürlich etwas Arbeit zu investieren, die aber nicht gescheut werden sollte, da als Lohn der Zugang zur faszinierenden Welt der regulären Ausdrücke winkt, die auch "alte Hasen" gelegentlich recht ordentlich verblüfft.
Was ist nun konkret Sache? Die Theoretiker beantworten diese Frage mit der Feststellung, dass sich mittels regulärer Ausdrücke genau die regulären Sprachen beschreiben lassen, also diejenigen, denen lineare Grammatiken zugrunde liegen und die genau von den endlichen Automaten akzeptiert werden. Punkt. Das ist korrekt, trägt aber kaum zur Demystifizierung der Angelegenheit bei. Aber keine Angst. Es ist zum Glück nicht nötig, diesen Satz zu verstehen oder gar tiefer in die Automatentheorie bzw. die Theorie der formalen Sprachen einzudringen. Wie so oft im Leben geht es auch einfacher und praxisorientierter.
Vor der Diskussion der konkret bei VIM 5.3 zur Verfügung stehenden regulären Ausdrücke sei darauf hingewiesen, dass es, speziell im UNIX-Umfeld, neben Vi & Co. viele Werkzeuge und Sprachen gibt, die reguläre Ausdrücke unterstützen. Zu ihnen zählen z.B. grep / egrep, lex, ed, sed, awk, Perl, Tcl und Python.
Um es dem Anwender nicht ganz so einfach zu machen, unterscheiden sie sich jeweils mehr oder minder geringfügig hinsichtlich des angebotenen Leistungsumfangs und der verwendeten Syntax :-). Davon sollte man sich nicht weiter beeindrucken oder gar abschrecken lassen. Wenn einmal das Prinzip klar ist, reichen in der Regel einige Versuche am betreffenden Objekt aus, um die gewünschten Resultate zu erzielen.
Trial and error ist eben oft ein brauchfähiger Weg. Der VIM kann dabei als eine sehr gute, weil interaktive Übungsumgebung angesehen werden. Geht mal was schief, ist das kein Desaster. Nach einem Undo ist ja der alte Zustand wieder da, und ein neuer Versuch klappt meist schon besser.
Wie immer gilt: "Übung macht den Meister" oder "Versuch macht klug".
Ein Muster (pattern) besteht aus mindestens einem Zweig (branch), wobei einzelne Zweige durch die Zeichenfolge \| zu trennen sind. Dieses Muster beschreibt die Vereinigung der Mengen der Zeichenketten, die durch die einzelnen Zweige angegeben werden. Das Trennzeichen ist auch als logisches Oder interpretierbar.
Beispiel:
abc\|def umfasst sowohl abc als auch def.
Ein einzelner Zweig besteht aus mindestens einer Gruppe (piece). Mehrere Gruppen werden bei Bedarf einfach ohne Trennzeichen aneinandergereiht. Ein Zweig beschreibt alle Zeichenketten, die sich aus der Verkettung der durch die einzelnen Gruppen spezifizierten Teilzeichenketten ergeben.
Beispiel:
abc[0-9]def umfasst die Zeichenketten, die mit abc beginnen, dann eine Ziffer (0..9) folgen lassen und mit def enden.
Eine Gruppe besteht aus einem Atom (atom), dem einige spezielle Zeichen bzw. Zeichenpaare folgen können, die angeben, wie oft die durch das Atom beschriebene Teilzeichenkette auftreten darf. Dabei ist zwischen gesetzter und rückgesetzter Option magic zu unterscheiden.
Spezielle Zeichen hinter einem Atom:
magic nomagic Anzahl des Auftretens des Atoms * \* 0 bis unendlich, so groß wie möglich \+ \+ 1 bis unendlich, so groß wie möglich \= \= 0 oder 1 \? \? 0 oder 1 \{n,m} \{n,m} n bis m, so groß wie möglich \{n} \{n} n \{n,} \{n,} n bis unendlich, so groß wie möglich \{,m} \{,m} 0 bis m, so groß wie möglich \{} \{} 0 bis unendlich, so groß wie möglich (äquivalent zu *) \{-n,m} \{-n,m} n bis m, so klein wie möglich \{-n} \{-n} n \{-n,} \{-n,} n bis unendlich, so klein wie möglich \{-,m} \{-,m} 0 bis m, so klein wie möglich \{-} \{-} 0 bis unendlich, so klein wie möglich
Beispiele:
magic nomagic dadurch beschrieben .* .\* ganze Zeile, auch wenn leer .\+ .\+ jede nichtleere Zeichenkette guu\=t guu\=t gut oder guut
Hier eine Auswahl verfügbarer Atome:
ein Zeichen aus der folgenden Tabelle:
magic nomagic Bedeutung ^ ^ Zeilenbeginn, sofern es das erste Zeichen des Musters ist $ $ Zeilenende, sofern es vor \| oder am Musterende steht . \. beliebiges einzelnes Zeichen \< \< Beginn eines Wortes \> \> Ende eines Wortes \s \s Whitespace-Zeichen (Leerzeichen oder Tabulator) \S \S kein Whitespace-Zeichen (Gegenstück zu \s) \d \d Ziffer ([0-9]) \D \D keine Ziffer ([^0-9]) \x \x Hexadezimal-Ziffer ([0-9A-Fa-f]) \X \X keine Hexadezimal-Ziffer ([^0-9A-Fa-f]) \o \o Oktal-Ziffer ([0-7]) \O \O keine Oktal-Ziffer ([^0-7]) \w \w Zeichen im Wort (word character, [0-9A-Za-z_]) \W \W kein Zeichen im Wort (non-word character, [^0-9A-Za-z_]) \h \h Zeichen am Wortanfang (head of word character, [A-Za-z_]) \H \H kein Zeichen am Wortanfang (non-head of word character, [^A-Za-z_]) \a \a Buchstabe ([A-Za-z]) \A \A kein Buchstabe ([^A-Za-z]) \l \l Kleinbuchstabe ([a-z]) \L \L kein Kleinbuchstabe ([^a-z]) \u \u Großbuchstabe ([A-Z]) \U \U kein Großbuchstabe ([^A-Z]) \e \e das Zeichen ESC (Escape) \t \t das Zeichen HT (Horizontal-Tabulator) \r \r das Zeichen CR (Carriage Return) \b \b das Zeichen BS (Backspace) ~ \~ entspricht der zuletzt spezifizierten Ersetzungs-Zeichenkette
ein Sub-Ausdruck (sub-expression). Dabei handelt es sich um ein Muster, das in \( und \), d.h. ein Paar runder Klammern, denen jeweils ein Backslash vorangeht, eingeschlossen ist. Ein Sub-Ausdruck beschreibt das eingeschlossene Muster, auf dessen konkreten Wert weiter hinten im Suchmuster oder in der bei einer Substitution angegebenen Ersatz-Zeichenkette Bezug genommen werden kann.
Beispiel:
\(^a\) für das Zeichen a am Zeilenanfang
\1
\2
...
\9
Diese Angaben der Form \n entsprechen jeweils dem konkreten Wert des n-ten in \( und \) eingeschlossenen Sub-Ausdrucks.
Beispiel:
\([a-z]\).\1 beschreibt Zeichenketten der Länge 3 Zeichen, deren Anfangs- und Endzeichen derselbe Buchstabe ist, z.B. uhu, ehe oder ata.
ein einzelnes Zeichen ohne Sonderbedeutung, das für sich selbst steht
ein Backslash gefolgt von einem einzelnen Zeichen ohne Sonderbedeutung, wobei hier der Backslash keine Bedeutung hat und ignoriert wird
Diese Notation ist für künftige Erweiterungen reserviert.
ein Bereich (range). Dabei handelt es sich um die Angabe einer Menge, aus der ein einzelnes Zeichen stammen kann. Der Bereich wird durch eine in eckige Klammern ([]) eingeschlossene Zeichenfolge definiert. Der ersten Klammer geht bei nomagic ein Backslash voraus (\[]).
Falls die Zeichenfolge mit einem ^ beginnt, so gehören diejenigen Zeichen zum Bereich, die nicht explizit genannt sind. Werden zwei Zeichen durch ein Minus-Zeichen (-) getrennt, so ist das eine Abkürzung für die Liste der ASCII-Zeichen, die beim erstgenannten Zeichen beginnt und beim letztgenannten endet, z.B. 0-9 für alle Ziffern 0 bis 9.
Die vier Sonderzeichen ]^-\ können in einen Bereich aufgenommen werden, wenn man ihnen einen Backslash voranstellt.
Beispiele:
[xyz\]]
[\^xyz]
[xy\-z]
[xyz\\]
Daneben gibt es einige Sonderregeln:
Die schließende eckige Klammer (]) gehört auch dann zu einem Bereich, wenn man sie als erstes Zeichen hinter der öffnenden Klammer bzw. dem negierenden ^ angibt.
Beispiel:
[]xyz] oder [^]xyz]
Ein Minus-Zeichen (-) gehört auch dann zu einem Bereich, wenn seine Spezifikation am Anfang bzw. am Ende erfolgt.
Beispiele:
[-xyz]
[^-xyz]
[xyz-]
Sofern ein Backslash keinem der Zeichen ^]-\etrb vorausgeht, wird er als Bestandteil des Bereichs betrachtet. So beschreibt z.B. die Notation
[\xyz]einen Bereich, zu dem die vier Zeichen \xyz gehören. Es ist allerdings besser, konsequent \\ zu verwenden, weil möglicherweise in künftigen VIM-Versionen weitere Backslash-Sequenzen eine Sonderbedeutung haben.
Anstelle einzelner Zeichen kann man bei der Beschreibung eines Bereichs auch Zeichenklassen angeben. Hier eine Auswahl:
Es ist zu beachten, dass jeder dieser Zeichenklassen-Spezifikatoren durch eckige Klammern begrenzt wird.
[:alnum:] Buchstaben und Ziffern [:space:] Whitespace-Zeichen [:upper:] Großbuchstaben [:xdigit:] Hexadezimal-Ziffern
Beispiel:
[-./[:alnum:]_~]\+
Anmerkungen:
Die Option ignorecase bewirkt, dass bei einer Suche Groß- und Kleinschreibung nicht unterschieden werden.
Es ist nicht möglich, einen Zeilenumbruch im Muster zu beschreiben, weswegen sich auch ein Muster immer auf genau eine Zeile bezieht.
Control-Zeichen können in Mustern enthalten sein, wobei deren Eingabe wie üblich durch Ctrl-V einzuleiten ist. Soll also ein Carriage Return gesucht werden, so muss Ctrl-V Ctrl-M eingegeben werden.
Einige weitere Details zu Suchmustern sind dem Referenzhandbuch zu entnehmen oder evtl. auch auszuprobieren.
Beispiele:
^beep( beep( am Zeilenanfang [a-zA-Z]$ jeder Buchstabe am Zeilenende hier \(vorn\|hinten\) sind Zeile, in der zwischen den Wörtern hier und sind eines der Wörter vorn oder hinten steht, jeweils durch Leerzeichen getrennt
Beim Ersetzungs-Kommando :substitute folgt hinter dem Argument, das die zu ersetzenden Zeichenketten durch ein Such-Muster, d.h. durch einen regulären Ausdruck beschreibt, ein weiteres Argument, das die Ersatz-Zeichenkette angibt. Diese kann Rückbezüge auf Teile der zu ersetzenden Zeichenkette enthalten. Der konkrete Wert eines solchen Rückbezugs ist nicht statisch, sondern hängt jeweils von der aktuell unter Verwendung des Such-Musters gefundenen Zeichenkette ab.
Einige Zeichen der Ersatz-Zeichenkette haben daher eine Sonderbedeutung. Sofern es sich um Ctrl-Zeichen handelt, sind diese mit vorangestelltem Ctrl-V einzugeben. Ggf. kann die Sonderbedeutung durch Voranstellen eines Backslash aufgehoben werden.
magic nomagic Bedeutung & \& Ersatz durch die komplette über das Such-Muster gefundene (und daher zu ersetzende) Zeichenkette \& & Ersatz durch & \0 Ersatz durch die komplette gefundene Zeichenkette \1 Ersatz durch die dem ersten Sub-Ausdruck entsprechende Zeichenkette \2 Ersatz durch die dem zweiten Sub-Ausdruck entsprechende Zeichenkette ... ... \9 Ersatz durch die dem neunten Sub-Ausdruck entsprechende Zeichenkette ~ \~ Ersatz durch die Ersatz-Zeichenkette der vorigen Substitution \~ ~ Ersatz durch ~ (Tilde) \u Ersatz durch das groß geschriebene Folgezeichen \U Ersatz durch die groß geschriebenen Folgezeichen \l Ersatz durch das klein geschriebene Folgezeichen \L Ersatz durch die klein geschriebenen Folgezeichen \e Beendigung von \U, \L, \u und \l \E dieselbe Wirkung wie \e Ctrl-M Zeilenumbruch an dieser Stelle \r wirkt wie Ctrl-M (Zeilenumbruch) Ctrl-V Ctrl-M symbolisiert das Zeichen CR (Carriage Return) \n symbolisiert das Zeichen NUL (Zeichen mit Dezimalwert 0) \b symbolisiert das Zeichen BS (Backspace) \t symbolisiert das Zeichen HT (Horizontal-Tabulator)
Beispiele:
Nachfolgend werden einige Ersetzungen gezeigt, die alle das Kommandos :s nutzen. Dessen genaue Syntax kann der Online-Hilfe entnommen werden.
:s/a\|b/xxx\0xxx/g ersetzt in der aktuellen Zeile alle Zeichen a gegen xxxaxxx und alle Zeichen b gegen xxxbxxx :s/\([abc]\)\([efg]\)/\2\1/g kehrt in der aktuellen Zeile die Reihenfolge der Zeichen aller Zeichenpaare um, die mit einem der drei Zeichen abc beginnen und mit einem der drei Zeichen efg enden :10s/abcde/abc^Mde/ fügt in Zeile 10 beim ersten Auftreten der Zeichenfolge abcde zwischen c und d eine Zeilenschaltung ein :.,$s/$/\r/ fügt im Bereich von der aktuellen Zeile bis zum Dateiende an jede Zeile eine weitere Zeilenschaltung an :%s/^M//g löscht alle Carriage-Return-Zeichen im Text :s/.*/& &/ fügt an den bisherigen Inhalt der aktuellen Zeile ein Leerzeichen und anschließend den bisherigen Inhalt noch einmal an So wird z.B. aus der alten Zeile abc die neue Zeile abc abc.
:s/\<[a-z]\+\>/\U&\e!/ wandelt alle Zeichen des ersten nur aus Kleinbuchstaben bestehenden Wortes der aktuellen Zeile in Großbuchstaben um und hängt ein ! an Anmerkung: Sofern die Option ignorecase gesetzt ist, erfolgt keine Beachtung der Groß- und Kleinschreibung. D.h., es wird das erste nur aus Buchstaben (Klein- und Großbuchstaben) bestehende Wort der aktuellen Zeile in angegebener Weise verändert.
Auch bei den Kommandos :g und :v lassen sich die zu bearbeitenden Zeilen mittels regulärer Ausdrücke spezifizieren:
:g/^$/d löscht alle leeren Zeilen, d.h. diejenigen, die lediglich aus der Zeilenendekennzeichnung bestehen :g/^(/s/a/b/g in allen Zeilen, die mit einer runden öffnenden Klammer beginnen, werden alle Buchstaben a gegen b ersetzt :v/z$/d löscht alle Zeilen, die nicht auf z enden