Reguläre Ausdrücke

Aus LinuxUser 08/2002

Reguläre Ausdrücke

Nadel im Heuhaufen

Regulären Ausdrücken begegnet man auf Unix-Systemen an allen Ecken und Enden. Doch was genau hat es damit auf sich, und wie nutzt man sie?

Reguläre Ausdrücke oder Regular Expressions sind kurz gesagt nichts weiter als Platzhalter für bestimmte Zeichenfolgen. Zum Einsatz kommen Regexps, wie man sie liebevoll abkürzt, bei Suchvorgängen unterschiedlichster Art, zum Beispiel in Texteditoren oder einer der zahlreichen Unix-Skriptsprachen.

Unzählige Sonderfunktionen und Specials machen reguläre Ausdrücke zu einer etwas unangenehmen und trockenen Materie. Wenn Sie sich das erste Mal durch die Grundlagen kämpfen, haben Sie mein aufrichtiges Mitgefühl. Aber glauben Sie mir, es lohnt sich!

Wo man auf Regexps trifft

Wer häufig auf der Kommandozeile arbeitet, nutzt grep für die Suche in Dateien. Dieses Tool verdankt regulären Ausdrücken sogar seinen Namen: Der ed-Befehl g/re/p druckt alle Zeilen einer Datei aus, die den regulären Ausdruck re enthalten – und das ist genau das Funktionsmuster von grep.

grep benötigt als Kommandozeilenargument zumindest den Suchausdruck. Die Datei(en), in der oder in denen gesucht werden soll, geben Sie als zusätzliche Argumente mit an. Sie wollen alle Nutzer Ihres Systems auflisten, deren Username mit dem Buchstaben t beginnt und die bash als Login-Shell nutzen?

grep -i ^t.*bash /etc/passwd

holt sie auf den Bildschirm.

Fehlen Dateinamen in der grep-Kommandozeile, so sucht das Tool in der Standardeingabe. Das ist ganz praktisch, wenn Sie die Ausgabe eines anderen Programms bearbeiten wollen. Wenn ich zum Beispiel auf der Suche nach Konqueror-Prozessen des Users mas bin, könnte ich folgenden Befehl verwenden:

ps -auwx | grep mas.*konqueror

Tabelle 1: Nützliche Optionen für <C>grep<C>

Befehl Bedeutung
-i Groß- und Kleinschreibung ignorieren
-l Nur die Dateinamen anzeigen, in denen der Ausdruck gefunden wird, nicht die zugehörigen Textzeilen
-v Suche umkehren: Nur die Textzeilen anzeigen, die den Suchausdruck nicht enthalten
-3 Nicht nur die Textzeile(n) mit dem Suchausdruck anzeigen, sondern zusätzlich noch je drei Zeilen davor und danach
-A 3 Nach der Zeile mit dem Suchausdruck werden zusätzlich noch die drei folgenden Zeilen angezeigt
-B 3 Vor der Zeile mit dem Suchausdruck werden zusätzlich die drei vorhergehenden Zeilen angezeigt

Die Suchfunktionen / (vorwärts) und ? (rückwärts) im Texteditor vi berücksichtigen immer reguläre Ausdrücke. In emacs mit seiner inkrementellen Suche würde das eher Verwirrung schaffen. Dort fahnden Sie erst auf ausdrücklichen Wunsch nach einer Regexp. Der Befehl für die gewohnte inkrementelle Suche heißt M-C-s, also [Strg-Alt-S]. Etwas geradliniger funktioniert M-x [Alt-X] search-forward-regexp: Dabei geben Sie den Suchausdruck erst einmal komplett ein und suchen anschließend.

Viele Leute lernen Regexps über perl kennen, doch auch die meisten anderen Skriptsprachen (darunter Tcl, Python und PHP) verstehen sie. Weniger bekannte (und trotzdem leistungsstarke) Regexp-Nutzer sind kleine Sprachen wie sed oder awk. Aber auch das C++-GUI-Toolkit Qt, das u. a. zum Schreiben von KDE-Programmen benutzt wird, kennt reguläre Ausdrücke.

Grundbausteine

Bei einfachen regulären Ausdrücken schreiben Sie im Prinzip ganz simpel hin, was Sie suchen möchten. Die meisten ASCII-Zeichen dürfen direkt benutzt werden: Auch ein einzelner Buchstabe ist bereits ein – wenn auch primitiver – Suchausdruck.

Die nächste Lektion lautet: Bestimmte Sonderzeichen aktivieren Sonderfunktionen. Das am meisten genutzte ist der Punkt: Er steht für ein beliebiges Zeichen. Die Regexp Hu.. sucht nach Hu, gefolgt von zwei beliebigen anderen Zeichen. Sie passt also gleichermaßen auf “Huhn” und “Hund”, auch auf “Hut ” (mit einem Leerzeichen danach), nicht aber auf einen einsamen “Hut” ohne ein nachfolgendes Zeichen.

Der Caret ^ sucht nach einem Zeilenanfang. Man nennt dies einen Anker: Es verankert den Suchausdruck am Anfang einer Zeile. Mit ^Huhn fahndet man nach einem Huhn am Zeilenbeginn.

Ganz analog steht das Dollarzeichen $ für ein Zeilenende. Der letzte Hund einer Zeile wäre also Hund$.

Weil ein regulärer Ausdruck normalerweise Zeile für Zeile überprüft wird, ergeben zusätzliche Zeichen vor dem ^ oder nach dem $ in der Regel keinen Sinn. Sprachen wie Perl erlauben da manchmal Ausnahmen – das sehen wir uns später noch an.

Den eigentlichen Reiz machen jedoch erst Kombinationen aus! ^Eins$ passt auf alle Zeilen, die genau das Wort “Eins” enthalten und sonst nichts. Eine leere Zeile finden Sie dementsprechend mit ^$: Gleich hinter dem Zeilenbeginn steht schon das Zeilenende, dazwischen befinden sich keinerlei Zeichen.

Achtung: Konstrukte wie ^ und $ repräsentieren in der durchsuchten Datei kein Zeichen, sondern sozusagen die Stelle zwischen zwei Zeichen.

grep ^a$ textdatei

beispielsweise gibt für jeden Treffer genau zwei Zeichen aus, ein “a” und einen Zeilenumbruch, obwohl der reguläre Ausdruck drei Stellen lang ist.

Alternativen und Wiederholungen

Manchmal ist nicht so ganz klar, was man genau sucht. Wenn sich ein Zeichen (genauer: ein Ausdruck) wiederholen darf, verwenden Sie einen der folgenden Operatoren: Der Stern * zeigt an, dass ein Zeichen in beliebiger Zahl vorkommen darf. a* steht also für ein oder mehrere “a”s – oder auch für den leeren String, der schließlich genau null “a”s enthält und damit die Forderung nach “beliebig vielen” erfüllt. Me*r passt demnach auf “Meer” oder “Meeeeeeer” genauso wie auf “Mr”.

Im Gegensatz dazu wiederholt das Pluszeichen + ein Zeichen: Mindestens einmal muss es vorkommen, aber gerne auch öfter. Der reguläre Ausdruck jj+n sucht nach zwei “j”s, von denen eines wiederholt werden darf, gefolgt von einem “n”. Damit passt er z. B. auf “jjn” oder “jjjjjjjjjn”, aber nicht auf “jn”.

Meistens benutzt man die Wiederholungsangaben + und * nicht im Zusammenhang mit vorgegebenen Zeichen, sondern man wiederholt den Punkt, der irgendein anderes Zeichen ersetzt. Der reguläre Ausdruck .* steht für eine beliebige Anzahl beliebiger Zeichen, passt also auf jede nur erdenkliche Textzeile. Die Regexp Jo.*nes ihrerseits passt auf jede Zeile, die ein “Jo” und irgendwo danach “nes” enthält, also z. B. auf “Jones”, “Johannes” oder auch “Joghurt ist ein feines Stück Lebensfreude”.

.* sucht sich immer einen möglichst langen passenden Abschnitt. Der Ausdruck a.*b passt im String “abcabcabc” auf “abcabcab”. Wenn das unerwünscht ist, hängen Sie in Perl ein Fragezeichen an. .*? sucht einen möglichst kurzen passenden Abschnitt, a.*?b findet in “abcabcabc” also gerade noch “ab”.

Ein isoliertes Fragezeichen ? verwenden Sie für optionale Anteile: Der vorangehende Buchstabe darf genau null- oder einmal vorkommen. ab?c steht für “ac” oder “abc”.

Seltener benötigt und hier nur der Vollständigkeit halber erwähnt ist die Möglichkeit, Wiederholungen zahlenmäßig zu bestimmen. Das tun Sie mit einer geschweiften Klammer, die Mindest- und Höchstzahlen enthalten kann. a{1,7} bezeichnet ein bis sieben “a”s. a{,7} steht für maximal sieben, a{4,} für mindestens vier “a”s.

Mit dem Pipe-Zeichen geben Sie alternative Suchausdrücke an. So zeigt

grep -E '(Bus|Bahn|Flugzeug)' Verkehrsmittel.txt

nur die öffentlichen Verkehrsmittel an. Das grep-Flag -E sagt dem Suchwerkzeug, dass es mit einem “erweiterten” regulären Ausdruck gefüttert wird. Ohne diese Option interpretiert es die Suchangabe als das gesuchte Wort.

Zeichenbereiche

Ein Zeichenbereich ist ein Ausdruck, der für eines von mehreren Zeichen steht, zum Beispiel nur für Buchstaben oder nur für Umlaute. Interaktiv in emacs oder vi wird man das wohl nicht so oft einsetzen, aber in Skriptsprachen sind Zeichenbereiche ganz praktisch, etwa wenn man bei der CGI-Programmierung sicher stellen kann, welche Zeichen als Eingabe erlaubt sind.

Bereiche werden durch eckige Klammern definiert. Dazwischen steht, welche Zeichen erlaubt sind. [abc] umfasst “a”, “b” oder “c”. Solche Bereiche lassen sich nach Lust und Laune mit anderen Sonderzeichen kombinieren: [abc]+ passt auf “a”, “aa”, “abababc” usw.

Bindestriche (alias Minuszeichen) erleichtern die Definition größerer Bereiche. [a-z] steht für einen beliebigen Kleinbuchstaben, [a-zA-Z0-9] für ein alphanumerisches Zeichen. Wenn das Minus selbst im Bereich enthalten sein soll, stellen Sie es an den Anfang: Der Bereich [-+a-z] enthält alle Kleinbuchstaben sowie Plus und Minus.

Das Caret ^ als erstes Zeichen innerhalb der eckigen Klammern kehrt die Bedeutung einer Bereichsdefinition um: Die angegebenen Zeichen dürfen nicht vorkommen. Alle Zeichen außer dem Z finden Sie mit [^Z]. Wenn Sie aus einer Datei alle Zeilen sehen möchten, die nicht mit Y oder Z beginnen (und auch nicht leer sind), schreiben Sie ^[^YZ]. Das Caret am Anfang der Regexp steht für einen Zeilenbeginn, unmittelbar danach muss ein beliebiges Zeichen außer Y oder Z stehen.

Vordefinierte Bereiche

Die meisten Bibliotheken für reguläre Ausdrücke definieren besondere Kürzel für oft vorkommende Bereiche, sodass Sie sie nicht jedesmal ausschreiben müssen. An dieser vorsichtigen Formulierung merken Sie schon, dass es sich dabei um keinen universalen Standard handelt …

Die wichtigsten vordefinierten Bereiche finden Sie in Tabelle 2 zusammengestellt. Dabei sind zum Einen die so genannten POSIX-Charakterklassen angegeben, die in vielen Engines erlaubt sind, unter anderem in grep oder Perl. Zum Anderen finden Sie in der Tabelle auch die Perl-eigenen Kürzel: Diese sind etwas kryptischer, aber dafür viel tastaturfreundlicher.

Tabelle 2: Von POSIX und Perl vordefinierte Bereiche

POSIX-Charakterklasse Bereichskürzel in Perl Bedeutung
[[:digit:]] \d “Zahl”: eine Ziffer von 0 bis 9
[^[:digit:]] \D “keine Zahl”: alles außer den Ziffern
[[:alpha:]] “alphabetisch”: Buchstaben (einschließlich ortsüblicher Sonderzeichen: Umlaute, akzentuierte Zeichen und Ähnliches)
[[:alnum:]] “alphanumerisch”: Buchstaben und Zahlen
[[:word:]] \w “Wort”: alphanumerisches Zeichen oder Unterstrich “_” (gehört nicht zu POSIX!)
[^[:word:]] \W “nicht Wort”: weder alphanumerisch noch Unterstrich
[[:lower:]] Kleinbuchstaben
[[:upper:]] Großbuchstaben
[[:punct:]] Interpunktionszeichen
[[:space:]] \s “Whitespace”: Leerzeichen, Tabstopp oder Zeilenumbruch, bei POSIX auch der seltene senkrechte Tabstopp
[^[:space:]] \S “kein Whitespace”: alles außer Leerzeichen, Tabstopp oder Zeilenumbruch
[[:blank:]] “waagerechter Whitespace”: Leerzeichen oder Tabstopp

Ihr großer Vorteil gegenüber handgestrickten Bereichen wie [a-zA-Z]: Die vordefinierten Versionen berücksichtigen in der Regel die Locale, sodass beispielsweise in Deutschland auch Umlaute oder Vokale mit Akzent zu den alphanumerischen Zeichen gerechnet werden.

Ein zweiter großer Vorteil ist unter Linux wohl irrelevant: Theoretisch wären ASCII-inkompatible Zeichensätze denkbar, in denen beispielsweise die Buchstaben des Alphabets nicht unmittelbar aufeinander folgen oder nicht korrekt sortiert sind. “[a-z]” enthielte dann möglicherweise nicht alle Buchstaben, dafür eventuell aber noch Sonderzeichen. [[:lower:]] enthält garantiert alle Kleinbuchstaben und nichts anderes, egal, welcher Zeichensatz verwendet wird.

Sonderzeichen

Bei den meisten Implementationen regulärer Ausdrücke dürfen Sie Sonderzeichen (Umlaute, Tabstopps, Steuerzeichen usw.) benutzen. Schreiben Sie diese direkt in die Regexp hinein. Wenn das zu unpraktisch ist (z. B. weil ein Tabstopp in einem Programmlisting optisch nicht von Leerzeichen zu unterscheiden ist), verwenden Sie die in C und den meisten Shells übliche Notation, zum Beispiel \t für einen Tabstopp. Wenn Sie nach einem Backslash suchen, verdoppeln Sie ihn: \\.

Gruppen

Bevor sich dieser Artikel der Praxis zuwendet, noch ein letztes wichtiges Konstrukt. Wiederholungszeichen wie * gelten immer nur für das Zeichen bzw. genauer für den Ausdruck, der unmittelbar vor ihnen steht. In abc* wird nur das “c” wiederholt, diese Regexp passt also auf “abcc”, aber nicht auf “abcabc”.

Durch runde Klammern umschließen Sie eine Regexp zu einer Gruppe. (abc) bedeutet effektiv das gleiche wie abc, aber intern funktioniert die Suche ganz anders: abc enthält die drei Suchausdrücke “a”, “b” und “c”, die nacheinander gefunden werden müssen. (abc) enthält als einzelnen Suchausdruck die Gruppe “abc”. Auf diese können Sie nun weitere Funktionen anwenden: (abc)* wiederholt die gesamte Gruppe. Dieser Ausdruck findet “abcabcabc” ebenso wie “abc” oder den leeren String, aber nicht mehr “abcc”.

Die Shell frisst aus der Hand

Ein ganz wichtiger Grund, warum reguläre Ausdrücke so beliebt sind, ist die hoffnungslose Verwirrung, in die man beim Umgang mit ihnen immer wieder stürzt. Reguläre Ausdrücke sind ja soooo schön unleserlich, wenn sie kompliziert werden. Hier ein kleines Beispiel aus der perlfaq6-Manpage:

/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|\n+|.[^/"'\\]*)

Dieses Ungetüm soll Kommentare in einem C-Programm finden, dabei jedoch scheinbare Kommentarzeichen in Strings ignorieren. Nein, ich habe es nicht ausprobiert, und nein, auch Profis können sowas nicht mehr “lesen”, höchstens mühsam auseinanderklamüsern.

Richtig fies wird es durch allerlei Komplikationen. Reguläre Ausdrücke verwendet man in tausend verschiedenen Programmen, und natürlich hat jedes davon seine eigene Implementation mit eigenen Features und eigenen Ausnahmeregelungen.

Die erste Falle, in die man meistens tappt, sind die Backslashes, vor allem in Kombination mit den runden Klammern für die Gruppendefinition. Wir hatten festgestellt: (abc)+ sucht Wiederholungen der Zeichenfolge “abc”, z. B. auch “abcabc” oder “abcabcabc”. Leider müssen Sonderzeichen wie die runden Klammern oder das Plus oft erst durch einen Backslash aktiviert werden. Wenn Sie mit Perl arbeiten, heißt die Regexp korrekt (abc)+. In grep hingegen muss sie \(abc\)\+ lauten. egrep oder grep -E sucht nach “Extended Regular Expressions” und versteht (abc)+ direkt.

Backslashes mag die Shell auch sehr gerne, sogar zum Fressen gern. Eine kleine Demonstration gefällig? Der Befehl

echo \(abc\)\+

gibt den Text “(abc)+” aus – die Shell hat alle Backslashes “weggefressen”. Schützen Sie Ihre Sonderzeichen also mit einem zusätzlichen Backslash vor der Shell. grep \\\(abc\\\)\\+ funktioniert. Einfacher und lesbarer: Verwenden Sie Anführungszeichen, am besten einfache (also Apostrophen): grep ‘\(abc\)\+’ tut dasselbe und ist übersichtlicher.

Unterschiede der Bibliotheken

Bereits mehrfach habe ich angedeutet, dass verschiedene Programme mit regulären Ausdrücken etwas unterschiedlich umgehen. Solange man sich nur mit einem einzigen Tool beschäftigt, etwa während der Programmierung eines Perl-Skripts, macht das keine Probleme. Beim raschen Wechsel oder der gleichzeitigen Benutzung mehrerer Werkzeuge kann es schon etwas Unmut stiften.

Die Differenzen betreffen vor allem zwei Punkte. Erstens: Erwartet das Programm einen “einfachen” oder einen “erweiterten” regulären Ausdruck? Dabei geht es vor allem darum, ob die ganzen Sonderfunktionen, z. B. das + oder die runden Klammern, erst durch einen vorangestellten Backslash \ eingeschaltet werden müssen oder ob sie schon aktiv sind (und durch einen Backslash ausgeschaltet würden).

Faustregel: Die meisten Skriptsprachen verwenden erweiterte reguläre Ausdrücke, Sie können die hier vorgestellten Funktionen also direkt einsetzen. Zu grep gibt es besagte Version namens egrep, die ebenfalls erweiterte Regexps versteht. Die meisten Editoren und Kommandozeilenbefehle hingegen erwarten einfache reguläre Ausdrücke; wenn dabei irgend etwas nicht funktioniert, ergänzen Sie Backslashes an den strategischen Punkten.

Zweitens und in der Praxis nicht ganz so problematisch: Ausgefeilte Details und raffinierte Spezialfunktionen sind meistens private Erweiterungen, die selbstredend in anderer Software nicht mehr zur Verfügung stehen. Perl beispielsweise erlaubt Ihnen das Unterbringen von Kommentaren in regulären Ausdrücken und kennt Spezialfunktionen für virtuelle Ausdrücke (das sind “vorausschauende” Ausdrücke, die prüfen, ob nach der eigentlichen Regexp ein bestimmter Text folgt oder nicht, ohne dass dieser Text jedoch zur Regexp gehören würde) – für grep ergäbe so etwas gar keinen Sinn.

Rückbesinnung innerhalb eines Ausdrucks

Runde Klammern umschließen – wie wir bereits festgestellt haben – eine Zeichengruppe. Diese Gruppen hatten wir bislang nur für das Wiederholen von Zeichenfolgen eingesetzt.

Die wahre Aufgabe solcherlei Gruppierung liegt jedoch woanders: Eine Gruppe definiert einen Teil-String, auf den man später Bezug nehmen kann. Sobald die Software also einen Ausdruck innerhalb von runden Klammern findet, merkt sie ihn sich. An einer anderen Stelle können Sie diesen String wieder verlangen.

Ein Beispiel: Die Regexp (BusBahn) passt wahlweise auf einen Bus oder eine Bahn. Weil sie in runden Klammern steht, merkt sich die Software, was davon gefunden wurde – “Bus” oder “Bahn”. Die Zeichenfolge wird in einer Variablen abgespeichert, wobei die hierfür benutzten Variablen der Einfachheit halber von eins bis neun durchnummeriert werden. Variable eins enthält den Inhalt der ersten Gruppe, Variable zwei den der zweiten Gruppe usw.

Mit dem Konstrukt \1 ff. beziehen Sie sich anschließend auf diese Variablen. Nehmen wir die Datei Verbindungen.txt aus Listing 1 zur Hilfe, die die Verbindungen von Vilsbiburg über Landshut nach München enthält.

Listing 1

Beispieldatei Verbindungen.txt

VIB     mit   nach LA   mit   nach M
10:45   Bus   11:52     Bahn  13:05
10:49   Bahn  11:19     Bahn  12:05
11:45   Bus   12:54     Bus   15:10
12:45   Bus   13:51     Bahn  15:05
13:49   Bahn  14:19     Bahn  15:05

Nun laufe ich beim Umsteigen nicht gerne vom Busbahnhof zum Hauptbahnhof. Ich möchte also nur diejenigen Verbindungen angezeigt bekommen, bei denen ich entweder nur mit dem Bus oder nur mit der Bahn fahre. Dafür verwende ich folgenden Befehl:

egrep '(Bus|Bahn).*\1' Verbindungen.txt

Die Regexp sucht zunächst nach “Bus” oder “Bahn” und speichert den gefundenen Text in der Variablen eins ab. Danach muss eine beliebige Zeichenfolge stehen (“.*”) und anschließend noch einmal der Inhalt der Variablen \1. Wurde zunächst also “Bus” gefunden, muss die Zeile später nochmal “Bus” enthalten, bei “Bahn” muss ein zweites Mal “Bahn” vorkommen.

Ausgegeben werden demnach die folgenden Zeilen:

10:49   Bahn  11:19     Bahn  12:05
11:45   Bus   12:54     Bus   15:10
13:49   Bahn  14:19     Bahn  15:05

Rückbesinnung in Perl

Perl beherrscht diese so genannten backward references natürlich perfekt. Innerhalb eines regulären Ausdrucks werden die numerischen Spezialvariablen \1 bis \9 mit dem Inhalt der Gruppen belegt. Zusätzlich erstellt Perl jedoch noch ganz normale Perl-Variablen, mit denen Sie auf die Fundstücke auch außerhalb des regulären Ausdrucks zugreifen können. Der Inhalt von \1 landet dabei in der Perl-Variablen $1, der Inhalt von \2 in $2 usw.

Ein kleines Beispiel aus einem primitiven Parser:

/^\s*From\s+(\w+)\s+Type\s+(\w+)\s+Seq\s+(\d+)\s+DB\s+(\w+)\s*$/i
    or die "Invalid pattern in packet: \"$_\"";
my ($from, $type, $seq, $db) = ($1, $2, $3, $4);

Jetzt enthalten die Variablen $from, $type, $seq und $db die jeweils gefundenen Strings. Bei der Eingabe

From mas Type ACK Seq 4219 DB Pharma

ist also $from = “mas”, $type = “ACK”, $seq = 4219 und $db = “Pharma”.

Zusätzliche Perl-Funktionen

Unabhängig von Gruppen und Ähnlichem belegt Perl nach einer Suche noch weitere Variablen. Diese fasst Tabelle 3 zusammen.

Tabelle 3: Perl-Variablen nach einer erfolgreichen Suche

Name Bedeutung
$& Der letzte gefundene String, also das, worauf die Regexp gepasst hat.
$` (Apostroph rückwärts) Der Teil des Strings vor dem gefundenen Anteil.
$’ (Apostroph vorwärts) Der Teil des Strings nach dem gefundenen Anteil. Nach einer erfolgreichen Suche wird der gesamte durchsuchte String zerlegt in $`$&$’.
$+ Die letzte passende Gruppe. Wenn Ihr regulärer Ausdruck mehrere Gruppenkonstrukte enthält, von denen einige optional sind, greifen Sie hiermit auf den Inhalt der letzten Gruppe zu.

Ohne Zweifel ist die Regexp-Engine von Perl eine der leistungsfähigeren im Unix-Umfeld. Ein paar praktische Perl-Einzeiler ermöglichen das unkomplizierte Herumprobieren auch an der Kommandozeile.

Mit dem folgenden Befehl wirkt Perl als grep-Ersatz, dabei werden alle Zeilen ausgegeben, die den regulären Ausdruck abc enthalten:

perl -ne 'print if /abc/' dateiname.txt

Perl agiert auch als sed-Ersatz. Im Beispiel wird die Regexp abc durch den Text def ersetzt und das Ergebnis am Bildschirm angezeigt:

perl -pe 's/abc/def/g' dateiname.txt

Der nächste Befehl wirkt ähnlich, allerdings ersetzen wir abcinnerhalb der Datei durch def und lassen Perl eine Sicherheitskopie in dateiname.txt~ erstellen:

perl -pi~ -e 's/abc/def/g' dateiname.txt

Grundsätzlich verwenden Sie in Perl m/regexp/ oder einfach /regexp/ für die einfache Suche. Zum Suchen und Ersetzen benutzen Sie s/regexp/Ersatztext/. Nach dem letzten Schrägstrich können Sie einen der Modifikatoren aus Tabelle 4 anhängen.

Tabelle 4: Modifikatoren für reguläre Ausdrücke in Perl

Anhängsel an den letzten Schrägstrich Bedeutung
/i Suche ohne Beachtung von Groß- und Kleinschreibung.
/s Der Punkt passt auch auf Zeilenumbrüche innerhalb des durchsuchten Strings.
/m Der durchsuchte String darf mehrere Zeilen enthalten (ähnlich wie bei grep), wobei ^ und $ auch innerhalb des Strings immer für einen Zeilenanfang bzw. ein Zeilenende stehen.
/g Beim Suchen und Ersetzen: Nicht nur die erste Fundstelle wird ersetzt, sondern alle im String.

Fehlersuche

Komplexe reguläre Ausdrücke funktionieren selten auf Anhieb. Die Suche nach dem Fehler kostet Zeit und Nerven. Halten Sie Kaffee oder Cola bereit!

An der Kommandozeile sollte der erste Schritt immer das Voranstellen von echo sein. Möglicherweise isst die Shell mit und verschluckt einzelne Zeichen. Indem Sie sich den tatsächlich ausgeführten Befehl noch einmal anzeigen lassen, kommen Sie diesem Problem schnell auf die Schliche.

Doch auch in einem Skript tun Sie sich viel Gutes, indem Sie den gesuchten regulären Ausdruck, die Fundstelle oder zumindest Teile davon noch einmal anzeigen lassen. Nehmen Sie entsprechende Befehle ins Skript mit auf. Der Perl-Debugger erlaubt Ihnen das interaktive Austesten regulärer Ausdrücke.

Wenn das noch nicht hilft, zerlegen Sie den Ausdruck in kleinere Teile. Prüfen Sie, ob alle Bestandteile wirklich genau das tun, was Sie erwarten.

Ganz wichtig: Bleiben Sie geduldig.

Ausblick

In der gebotenen Kürze haben Sie nun einen Eindruck von den Fähigkeiten und Möglichkeiten, aber auch von der Komplexität der regulären Ausdrücke erhalten. Wenn Sie sich weiter damit beschäftigen wollen, hilft nur eines: üben, üben, üben. Die “Lektüre” bzw. das mühsame Auseinanderklauben der oft komplexen Regexps, die man in Skripten allenthalben findet, ist gleichzeitig lehrreich und frustrierend. Besser fährt man, wenn man reguläre Ausdrücke konsequent einsetzt. Spätestens dann, wenn Sie zwei ähnliche Suchen kurz hintereinander durchführen, fragen Sie sich: Wäre es mit einer Regexp nicht einfacher gegangen?

Weiterführende Informationen in englischer Sprache finden Sie in den Handbuchseiten zu grep und perlre. Die Perl-Dokumentation enthält darüber hinaus noch ein paar Tipps und Tricks, insbesondere auch zur Fehlersuche: man perltrap und man perlfaq6.

Glossar

ed
Der klassische Unix-Zeileneditor, bei dem man nicht wie bei modernen Texteditoren üblich eine ganze Datei auf einmal bearbeitet, sondern Befehle gibt, die sich auf eine oder mehrere bestimmte Zeilen beziehen.
CGI-Programmierung
Das Schreiben von Skripten oder auch kompilierten Programmen, die auf dem Web-Server abgelegt, beim Abruf einer entsprechenden Web-Seite aufgerufen werden und ein HTML-Dokument “on the fly” (dynamisch) generieren. Die Abkürzung steht für “Common Gateway Interface”.
POSIX
Ein Versuch der Normierung typischer Unix-Funktionalitäten und -Definitionen im IEEE-Standard 1003.1. Die meisten Linux-Programme sind auf Wunsch kompatibel zu POSIX, enthalten oft jedoch noch zusätzliche Funktionen.
Locale
POSIX unterstützt die automatische Anpassung von Programmen an die lokalen Verhältnisse. Offensichtliches Beispiel ist die automatische Anzeige deutscher (oder genauer: muttersprachlicher) Übersetzungen für Systemmeldungen oder Handbuchseiten. Zur Locale gehören aber auch Dinge wie das ortsübliche Format für Zahlen, Zeit- oder Datumsangaben, Maßeinheiten oder die bevorzugte Papiergröße und natürlich die Information darüber, welche Teile des Zeichensatzes welche Funktion erfüllen – ob beispielsweise das Zeichen 196 ein Ä oder ein nicht druckbares Sonderzeichen darstellt.

Der Autor

Marc André Selig arbeitet je zur Hälfte als wissenschaftlicher Angestellter an der Universität Trier und als Arzt im Praktikum am Kreiskrankenhaus Schramberg. Wenn es die Zeit zulässt, beschäftigt er sich momentan vor allem mit der Programmierung Web-basierter Datenbanken auf diversen Unix-Plattformen.

LinuxUser 08/2002 KAUFEN
EINZELNE AUSGABE
ABONNEMENTS
TABLET & SMARTPHONE APPS
E-Mail Benachrichtigung
Benachrichtige mich zu:

Hinweis: Dieser Artikel ist älter als ein Jahr, enthaltene Informationen sind möglicherweise veraltet.

0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben