Buntes mit Hilfestellung (Linux-Magazin, Oktober 2000)

Farbig angezeigte Treffer bei Suchabfragen in Textdateien. Und ein neues Verfahren, die Bedienungsanleitung zu einem Skript bei Bedarf anzuzeigen.

Wie schön wär's, wenn der Unix-grep endlich Perls reguläre Ausdrücke verstünde! Was gäb' ich, wenn in den passenden Zeilen auch noch die Treffer farbig markiert wären! Ja hätt' ich dieses Tool nur in meinem Werkzeugkasten! Heute macht der Perl-Onkel Träume wahr.

Das heute vorgestellte Skript cgrep bietet in der Tat die gewünschte Funktionalität. Es erwartet auf der Kommandozeile einen regulären Ausdruck und durchstöbert die angegebenen Dateien oder die Standardeingabe nach dem Suchmuster. Gefundene Zeilen gibt es aus und unterlegt die Treffer farbig.

Abbildung 1 zeigt, wie cgrep sein eigenes Listing nach dem Wort rex durchsucht. Das Kommando cgrep rex cgrep bringt sieben Zeilen zum Vorschein, die insgesamt neunmal das Wort rex lila unterlegen.

Abbildung 1

Abb.1: Suche nach dem Wort rex

Da cgrep nicht nur greps reguläre Ausdrücke versteht, sondern den vollen Perl-5-Satz, kann man, wie in Abbildung 2 demonstriert, auch nach alleinstehenden Ziffern suchen. Der reguläre Ausdruck hierzu lautet \b\d+\b: Eine Wortgrenze, gefolgt von einer oder mehreren Ziffern, gefolgt von einer abschließenden Wortgrenze. Dieses Muster findet zwar die Fünf in Perl 5, aber zum Beispiel nicht Perl5, da im zweiten Fall keine Wortgrenze zwischen Perl und 5 liegt.

Abbildung 2

Abb.2: Suche nach alleinstehenden Zahlen

Farbig schreiben

Das Modul Term::ANSIColor bietet die Funktion colored an, die einen ihr übergebenen Text mit Kontrollsequenzen versieht, die ihn farbig unterlegen, falls er auf einem Terminal ausgegeben wird. colored erwartet zwei Parameter: Einen Skalar, der den einzufärbenden Text enthält und einen weiteren Skalar, der die Malerarbeiten als Vorder- und Hintergrundfarbe beschreibt. 'white on_blue' selektiert beispielsweise weißen Text auf blauem Hintergrund:

    use Term::ANSIColor;
    
    print "Der ", 
          colored("Himmel", "white on_blue"),
          " über Bayern.\n";

Dabei exportiert das Modul Term::ANSIColor die Funktion colored automatisch in das Hauptprogramm, so dass dieses colored ohne Modulzusatz aufrufen kann. Mehr zu diesem Thema und weiteren nützlichen Funktionen von Term::ANSIColor findet sich in [1].

Wie funktioniert's?

Listing cgrep zeigt, wie's geht: Es zieht zunächst die Zusatzmodule Term::ANSIColor zum Einfärben, Getopt::Std zur Kommandozeilenanalyse und das weiter unten detailliert besprochene Pod::Usage zum eleganten Ausdrucken der Bedienungsanleitung herein. Wer noch nicht perl 5.6.0 fährt, muss die Module von Hand nachinstallieren, Abschnitt Installation zeigt wie.

Die Funktion getopts in Zeile 12 holt eventuell gesetzte Schalter -h, -p und -i von der Kommandozeile und setzt dementsprechend die Hasheinträge $opt{h}, $opt{p} oder $opt{i}.

Zeile 16 holt den regulären Ausdruck als String von der Kommandozeile, Zeile 19 druckt eine Fehlermeldung mit Hilfestellung aus und bricht das Programm ab, falls vergessen wurde, einen Ausdruck anzugeben. Falls der Benutzer cgrep den Schalter -i zum Ignorieren von Groß- und Kleinschreibung mitgab, stellt Zeile 22 den regulären Ausdruck den String (?i) voraus, was Perl -- wie der Modifizierer /.../i -- dazu veranlasst, beim Pattern-Matching keinen Wert auf Groß- oder Kleinschreibung zu legen.

Zeile 25 compiliert den Ausdruck, der ja als String vorliegt, mit dem qr-Operator in einen Regex-Ausdruck. Dies verursacht einen tödlichen Fehler, falls der Ausdruck syntaktisch nicht den Perl-5-Bedingungen entspricht. Das eval-Konstrukt fängt dies ab, gibt im Fehlerfall einen falschen Wert zurück und mit pod2usage eine Fehlermeldung einschließlich Hilfestellung aus.

Geht alles soweit gut, iteriert Zeile 29 über alle Zeilen der auf der Kommandozeile namentlich bereitgestellten Dateien, oder, falls sich keine Namen finden, über alle Zeilen aus dem Standard-Input.

Zeile 32 prüft, ob die aktuell analysierte Zeile dem regulären Ausdruck genügt. Falls sich ein Treffer findet, ruft das Suche-Ersetze-Konstrukt in den Zeilen 35-37 dafür die Färbefunktion colored aus dem Term::ANSIColor-Modul auf. Der Modifizierer /g stellt sicher, dass dies für alle Treffer in einer Zeile geschieht, und auch falls der reguläre Ausdruck aus einer Reihe von Alternativen (wie in /a|b|c/) besteht, sorgt die Treffervariable $& dafür, dass jeweils der richtige Text eingefärbt wird. Der Modifizierer /x erlaubt es, den Regex-Salat der Übersicht halber auf mehrere Zeilen zu verteilen, und /e sorgt dafür, dass perl den Ersetzungsausdruck als Code interpretiert, also tatsächlich die Funktion colored() aufruft und nicht eine sture Text-für-Text-Ersetzung durchführt.

Traf der Ausdruck die aktuelle Datenzeile nicht, druckt sie cgrep in Zeile 42 dennoch aus, falls der Benutzer den Schalter -p auf der Kommandozeile setzte.

Noch ein Beispiel: Zwei gleiche Worte, die direkt hintereinanderstehen, deuten meistens auf einen Tippfehler hin -- schau'n wir mal:

    cgrep '\b(\w+)\s+\1\b' cgrep

sucht nach einer Wortgrenze, einem Wort, Leerzeichen und das gleiche Wort nochmal in Folge, abgerundet von einer Wortgrenze. Und, in der Tat in Zeile 28 von Listing cgrep hat sich ein Fehler eingeschlichen, wie Abbildung 3 zeigt.

Abbildung 3

Abb.3: Huch! Ein Tippfehler in cgrep!

Das war's schon. Und damit auch jeder weiss, wie cgrep funktioniert -- auch die Leute, die zu faul zum Manualseitenlesen sind --, nutzt cgrep das Modul Pod::Usage, das mit der Funktion pod2usage die Möglichkeit bietet, die in ein Skript mit POD (Plain Old Documentation) eingebettete Dokumentation teilweise oder ganz im Textformat auszugeben.

Eine neue Manualseitengeneration

Jedes Perl-Modul enthält ja mittlerweile mit POD-Tags ausgezeichnete Dokumentation. Der perl-Interpreter ignoriert diese Information geflissentlich und Werkzeuge wie perldoc oder die Filter pod2man, pod2html etc. extrahieren sie auf Wunsch und wandeln sie in das gewünschte Zielformat um. Der Aufruf perldoc Skriptname generiert üblicherweise eine Manualseite aus dem Skript und zeigt sie an. Das Modul Pod::Usage hingegen erlaubt es, auf die eingebettete Dokumentation schon aus dem Skript heraus zuzugreifen -- üblicherweise falls seitens des Benutzers ein Eingabefehler vorliegt und das Skript die fällige Fehlermeldung mit einer mehr oder weniger detailierten Bedienungsanleitung versehen will.

pod2usage gibt üblicherweise eine ihr als String übergebene Fehlermeldung aus, und druckt außerdem die Sektionen SYNOPSIS, OPTIONS und ARGUMENTS einer eingebetteten Manualseite. Danach bricht pod2usage es das Skript ab. Der optionale Parameter -verbose steuert die Leutseligkeit des Programms -- für den Wert 0 gibt es nur die SYNOPSIS-Abteilung aus, für den Wert 1 die oben erwähnten drei Sektionen und für Werte größer gleich 2 die gesamte Manualseite. Die POD-Anweisungen nutzt perl dazu, einen ansprechend formatierten Text zu generieren. Typische Anwendungen von pod2usage sind:

        # SYNOPSIS und  OPTIONS/ARGUMENTS
    pod2usage( $message );
    
        # Nur SYNOPSIS
    pod2usage( -message => $message, 
               -verbose => 0 );
    
        # SYNOPSIS und  OPTIONS/ARGUMENTS
    pod2usage( -message => $message, 
               -verbose => 1 );
    
        # Gesamte Manualseite
    pod2usage( -message => $message, 
               -verbose => 2 );

Ruft so jemand das Skript cgrep ohne regulären Ausdruck auf, springt Zeile 19 ein und ruft pod2usage auf, welches die Ausgabe nach Abbildung 4 liefert, die dem Benutzer die richtige Anwendung des Programms nahelegt.

Abbildung 4

Abb.4: Hilfe nach Aufruf ohne Parametern

Verlangt der Benutzer hingegen explizit nach der Manualseite, indem er cgrep -h aufruft, verzweigt Zeile 14 zu pod2usage und stellt mit verbose-Level 2 den Dokumentationsmodus ein. Abbildung 5 zeigt die Textdarstellung im X-Terminal.

Abbildung 5

Abb.5: Vollständige Manualseite nach cgrep -h | less

Natürlich steht auch nichts im Wege, den POD-Text in cgrep im Unix-üblichen Manualseitenformat anzuzeigen:

    pod2man cgrep | nroff -man | less

extrahiert die Information aus dem Skript und übergibt sie dem Unix-üblich Manualformatierer nroff, der diese wiederum durch den Pager less pfeift. Und auch vor HTML scheut sich POD nicht:

    pod2html cgrep >cgrep.html

erzeugt HTML-Text aus dem POD-Markup und legt ihn in cgrep.html ab. Abbildung 6 zeigt, wie der Browser die Manualseite cgrep.html darstellt.

Abbildung 6

Abb.6: Die Manualseite als HTML im Browser

Listing cgrep

    001 #!/usr/bin/perl -w
    002 ##################################################
    003 # cgrep - Highlight regular expression matches
    004 #
    005 # Mike Schilli, 2000
    006 ##################################################
    007 
    008 use Term::ANSIColor;
    009 use Pod::Usage;
    010 use Getopt::Std;
    011 
    012 getopts( 'hpi', \%opt );
    013 
    014 pod2usage(-verbose => 2) if $opt{h};
    015 
    016 my $rex = shift;
    017 
    018     ### Called without regex?
    019 pod2usage("Missing argument") unless defined $rex;
    020 
    021     ### Regex modifiers if requested
    022 $rex = "(?i)$rex" if $opt{i};
    023 
    024     ### Compile regex and complain if it's broken
    025 eval { $rex = qr($rex) } or 
    026     pod2usage "Invalid Regex: $rex";
    027 
    028     ### Loop over all all lines of input
    029 while(<>) {
    030 
    031        ### Match?
    032     if( /$rex/ ) {
    033 
    034             ### Colorize with Term::ANSIColor
    035         s/$rex
    036          /colored($&, 'yellow on_magenta')
    037          /egx;
    038 
    039         print;
    040     } else {
    041             ### Print anyway if -p
    042         print if $opt{p};
    043     }
    044 }
    045 
    046 __END__
    047 
    048 =head1 NAME
    049 
    050 cgrep - Highlight regular expression matches
    051 
    052 =head1 SYNOPSIS
    053 
    054 cgrep [options] regex [file ...]
    055 
    056  Options:
    057    -h     get help
    058    -p     print all lines
    059    -i     ignore case
    060 
    061 =head1 OPTIONS
    062 
    063 =over 8
    064 
    065 =item B<-h>
    066 
    067 Prints this manual page in text format.
    068 
    069 =item B<-p>
    070 
    071 Prints all lines, no matter if they match or not.
    072 
    073 =item B<-i>
    074 
    075 Activate "ignore case" mode in the regular expression match, just like
    076 the perl regex modifier /.../i.
    077 
    078 =back
    079 
    080 =head1 DESCRIPTION
    081 
    082 B<cgrep> reads the files provided on the command
    083 line - or standard input in case there are none,
    084 just like C<grep> does.
    085 
    086 It takes a mandatory C<regex> parameter, which
    087 must be a valid Perl 5 regular expression. It
    088 will print out all lines matching the regex and
    089 colorize the matches accordingly.
    090 
    091 Be sure to escape regex metacharacters if they're
    092 meant literally (C<|> =E<gt> C<\|>). Quote the
    093 regex on the command line if it contains shell
    094 metacharacters.
    095 
    096 =head1 EXAMPLES
    097 
    098 Print all lines in C<myfile.dat> and
    099 C<yourfile.dat> containing the word I<main>
    100 and colorize all occurrences of the word C<main>:
    101 
    102     cgrep main myfile.dat yourfile.dat
    103 
    104 Print and colorize all numbers in file C<file>:
    105 
    106     cat file | cgrep '\b\d+\b'
    107 
    108 Print all lines of the file C<file> and colorize
    109 occurrences of the word C<word>:
    110 
    111     cgrep word file
    112 
    113 =head1 AUTHOR
    114 
    115 2000, Mike Schilli <mschilli1@aol.com>

Installation

Die Zusatzmodule Getopt::Std, Term::ANSIColor und Pod::Usage liegen schon der Distribution von perl 5.6.0 bei -- wer noch immer mit einer älteren Version herumgurkt, läd die beiden letzeren Schmankerln wie immer mit der CPAN-Shell nach. Die Befehlsfolge

    perl -MCPAN -eshell
    cpan> install Term::ANSIColor
    cpan> install Pod::Usage

installiert die Module auf der lokalen Maschine. Fröhliches greppen!

Referenzen

[1]
Die Manualseiten perldoc Pod::Usage und perldoc Term::ANSIColor

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.