Aus Linux-Magazin 02/2005

Shell-Funktionen in Perl nachgebaut

Mit dem neuen Modul Sysadm::Install wagt Perl den Vorstoß ins Stammland von Bash & Co. Perl-Fan Michael Schilli behauptet: So gelingen die besseren Shellskripte.

Mit ein paar Shellkommandos sind Skripte für die Systemadministration schnell zusammengeklopft: Eine Installationsroutine mit »cp«, »mv« und »chmod« hier, eine mit »grep«, »awk« und »sed« gezimmerte Abfrage dort. Kommen weitere Anforderungen hinzu, werden die Skripte komplizierter, oft unübersichtlich. Zuweilen endet der Skripter in einer Sackgasse: Manches lässt sich in der Shell eben nicht oder nur sehr umständlich realisieren.

Kreative Programmierer finden zwar immer (zuweilen obskure) Wege, um Klippen zu umschiffen, und Shells wie Bash, Ksh und Tcsh bieten manche Vorzüge einer echten Programmiersprache. Außerdem ist Standard-Perl bei vielen einfachen Aufgaben nichts für faule Tipper. Wer will schon

open FILE, "<Filename" or
    die "Cannot open filename ($!)";

schreiben, wenn ein einfaches »cat Filename« die Datei in der Shell öffnen und ausgegeben kann? Sysadm::Install, ein neues Modul vom CPAN, schafft Abhilfe. Es exportiert Funktionen wie »cp«, »mv«, »untar«, »mkd«, »rmf« (»rm -f«) oder »cd«, damit Shellskripter sich in Perl zu Hause fühlen.

Darüber hinaus kennt es Funktionen für interaktive Benutzerabfragen, für die Dateimanipulation, den Download sowie vereinfachte Schnittstellen zum Aufruf externer Programme.

Transformer

Topng in Listing 1 zeigt ein Skript, das eine Reihe JPG-Bilder mit Hilfe des Utility »convert« ins PNG-Format umwandelt. Es holt mit »use Sysadm::Install qw(:all)« alle verfügbaren Funktionen in den aktuellen Namensraum. Zwei davon nutzt es: »sysrun()«, um ein externes Programm ablaufen zu lassen, und »rmf()«, die Sysadm::Install-Version von »rm -f«. Der Aufruf von

topng *.jpg

in einem Verzeichnis voller JPGs zeigt lange nichts – dann sind die PNGs fertig. Mehr Informationen gefällig? Das ist leicht: Da Sysadm::Install das Log::Log4perl-System unterstützt, reicht ein

use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($DEBUG);

am Anfang des Skripts und schon wird es gesprächiger:

2004/12/03 23:25:20 sysrun: convert 1.jpg 1.png
2004/12/03 23:25:32 rmf 1.jpg
2004/12/03 23:25:32 sysrun: convert 2.jpg 2.png
2004/12/03 23:25:44 rmf 2.jpg

Aber das ist nicht der einzige Grund, warum das Skript »sysrun()« und »rmf()« aus dem Sysadm::Install-Fundus verwendet und nicht etwa Perls Standardfunktionen. »system()« und »unlink()«. »sysrun()« und »rmf()« laufen, wie alle Funktionen aus Sysadm::Install, in einem Run-or-die-Modus: Jedes Ergebnis wird hinter den Kulissen minutiös geprüft und das Skript bei einem Fehler sofort mit »die()« abgebrochen. Shellprogrammierer, die bislang immer

cp a b || exit 1
mv c d || exit 1

schreiben mussten, atmen sicherlich auf. Auch verwendet »topng« bewusst nicht den »strict«-Modus, der sonst in der Perl-Welt mit fast schon religiöser Strenge eingesetzt wird. Es ist ein Quick-and-dirty-Skript und es steht dazu.

Listing 1:
Topng

01 #!/usr/bin/perl -w
02 # topng -- Transform jpg pics to PNG
03 
04 use Sysadm::Install qw(:all);
05 
06 for $jpg (@ARGV) {
07     ($png = $jpg) =~ s/jpg$/png/;
08     sysrun("convert", $jpg, $png);
09     rmf($jpg);
10 }.

Elegant entblättert

Wer nicht täglich damit arbeitet, hat die passenden Optionen für »tar« vielleicht nicht immer im Kopf: Ist versehentlich die Option »cf« statt »xf« gesetzt, überschreibt »tar« ungewollt das Archiv, statt es zu entpacken. Außerdem gibt es immer noch Unbelehrbare, die kein einzelnes Top-Verzeichnis einpacken, sondern auf oberster Ebene alle möglichen Dateien hineinpfeffern, mit denen der Entpackende unfreiwillig sein aktuelles Verzeichnis zuschüttet. »untar()« macht Schluss damit. Es nimmt den Namen einer »tar«-Datei entgegen, findet heraus, ob eine Dekompression notwendig ist, und entpackt den Inhalt. Ist kein einzelnes Top-Verzeichnis enthalten, sondern ein wildes Tohuwabohu, erzeugt es ein Top-Verzeichnis und legt den Inhalt darin sauber ab.

Das Beispielskript »untar« in Listing 2 wird einfach mit dem Tarball-Namen aufgerufen, beispielsweise mit:

untar pari-2.1.4.tgz

Wer statt stoischer Ruhe lieber Informationen über den Ablauf wünscht, fügt die Option »-v« hinzu und erhält Details über die Transaktionen.

Listing 2:
Untar

01 #!/usr/bin/perl
02 # untar -- Untar tarballs
03 
04 use warnings;
05 use strict;
06 
07 use Log::Log4perl qw(:easy);
08 use Getopt::Std;
09 use Sysadm::Install qw(untar);
10 
11 getopts('v', my %opts);
12 
13 Log::Log4perl->easy_init(
14     $opts{v} ? $DEBUG : $ERROR);
15 
16 for my $tar (@ARGV) {
17    untar($tar);
18 }

Der Hammer: Perl in der Kaffeepause

Manches Installationsskript fordert den Benutzer zu interaktiven Eingaben auf. Meist reicht es, auf die [Enter]-Taste zu hämmern. Genau das ist es, was die Funktion »hammer()« macht. Ein typisches Beispiel ist der Perl-Build: Man lädt den Tarball von »perl.com«, entpackt ihn, springt ins oberste Verzeichnis, startet »./Configure« – und sieht sich mit tausend Fragen konfrontiert. Wer sicher ist, dass immer die voreingestellte Antwort passt, gibt entweder die Option »-d« an oder beginnt selbst auf die Eingabetaste zu hämmern.

Das »mkperl«-Skript in Listing 3 lädt den aktuellen stabilen Perl-Tarball mit »download()« von »perl.com«, entpackt das Archiv mit »untar()«, konfiguriert die Release und startet den Build. Es nutzt die »-d«-Option (verwende De-faultwerte) von »Configure«, da sich bei der Perl-Konfiguration nicht immer alles durchhämmern lässt, aber setzt »hammer()« für den letzten Prompt ein. An dieser Stelle fragt das Configure-Skript, ob der Benutzer eine Shell starten möchte, um die entstandene »config«-Datei manuell noch weiter zu editieren. Der automatisierte Druck auf die [Enter]-Taste verneint dies und schließt die Konfiguration ab.

Listing 3:
Mkperl

01 #!/usr/bin/perl
02 # mkperl - Download, configure and install
03 # the latest stable perl,
04 
05 use strict;
06 use warnings;
07 use Log::Log4perl qw(:easy);
08 Log::Log4perl->easy_init($DEBUG);
09 use Sysadm::Install qw(download
10          hammer untar cd sysrun);
11 
12 download "http://www.perl.com/" .
13          "CPAN/src/stable.tar.gz";
14 untar "stable.tar.gz";
15 cd "stable";
16 hammer("./Configure", "-d", "-D",
17        "prefix=/home/mschilli/PERL-test");
18 sysrun("make install");

Manchmal brauchen Skripte eine Bestätigung vom Benutzer: Stimmt der Defaultwert oder welche der fünf angebotenen Dateien ist die richtige? Dazu stellt Sysadm::Install die Funktionen »ask« und »pick« bereit. »ask« fragt den Benutzer einfach, ob ein vorgegebener Text übernommen oder neu eingegeben werden soll. »pick« stellt eine Reihe von Optionen zur Auswahl, nummeriert sie durch und lässt den Benutzer die gewählte Nummer eingeben. E

Das »input«-Skript (Listing 4) holt vom Anwender erst einen Textstring und lässt ihn eine von drei vorgegebenen Optionen auswählen:

Name [No-Name-Entered]> Bill Gates
  Name: Bill Gates
[1] 0-100K
[2] 100K-200K
[3] 300K-
Salary [1]> 3
  Salary: 300K-

Die Rückgabewerte von »ask« und »pick« entsprechen dem vom Benutzer eingegebenen beziehungsweise ausgewählten Wert, hier also »Bill Gates« (für den Textstring) und »300K-« (bei der Optionswahl).

Listing 4:
Input

01 #!/usr/bin/perl
02 # input -- Test ask() and pick()
03 
04 use warnings;
05 use strict;
06 
07 use Sysadm::Install qw(:all);
08 
09 my $name = ask "Name", "No-Name-Entered";
10 print "  Name: $namen";
11 
12 my $salary = pick "Salary",
13    ["0-100K", "100K-200K", "300K-"], 1;
14 print "  Salary: $salaryn";

Wer Perls Einzeiler einsetzt, kennt das Problem, den Perl-Code auf der Kommandozeile vor der gefräßigen Shell zu schützen. Wer naiv

perl -e "print "Hi $USER!n""

eintippt, wird feststellen, dass es nicht funktioniert. Die Shell frisst die inneren Anführungszeichen und den Backslash, das Ausrufezeichen holt vorher ausgeführte Kommandos zurück. Maskiert man die empfindlichen Zeichen mit einem Backslash (und diesen mit einem weiteren Backslash), klappt\’s:

perl -e "print "Hi $USER!\n""

Eine weitere Möglichkeit sind einfache Anführungszeichen, aber dann substituiert die Shell keine Variablen mehr und einfache Anführungszeichen im Code müssen maskiert werden.

Alle Sonderzeichen maskiert

Noch schlimmer wird es, wenn obiges Kommando statt auf dem lokalen Rechner per »ssh -t Rechner Kommando« auf einer anderen Maschine irgendwo im Netz läuft. Dann will jedes Sonderzeichen (und zwar auch die vorher maskierten Sonderzeichen und ihre Maskierer) wiederum maskiert sein. Schon stellt sich das heimelige Gefühl der Backslashitis ein:

ssh -t somehost "perl -e 
"print \"Hi\!\\n\"""

Wer derlei Akrobatik scheut, zieht einfach die von Sysadm::Install auf Wunsch exportierte Funktion »qquote()« heran. Sie setzt doppelte Anführungszeichen um einen ihr übergebenen String und maskiert im String enthaltene Quotes und Backslashes. Lautet der zweite ihr übergebene Parameter »:shell«, dann genießen auch das gefährliche Dollarsymbol, bedrohliche Ausrufezeichen und hinterhältige Backquotes ebenfalls diesen Schutz.

Listing 5 definiert ab Zeile 10 ein Skript, das das Kommando »ifconfig« ausführt und die IP-Adressen aller angezeigten Netzwerk-Interfaces extrahiert. Zeile 16 entfernt Zeilenumbrüche und überflüssige Leerzeichen aus dem Skripttext und »qquote()« in Zeile 19 formt daraus einen kompakten, in doppelte Quotes eingeschlossenen String, den das Skript hinter »perl -e« hängt.

Listing 5:
Ips

01 #!/usr/bin/perl
02 # ips -- run a script on a remote machine
03 
04 use warnings;
05 use strict;
06 
07 use Sysadm::Install qw(qquote);
08 
09 my $script = q{
10     $data = `ifconfig`;
11     while($data =~ /inet addr:(S+)/g) {
12        print "$1n";
13     }
14 };
15 
16 $script =~ s/s+/ /g;
17 
18 my $cmd = "perl -e " .
19     qquote($script, ":shell");
20 
21 $cmd = "ssh -t somehost " .
22        qquote($cmd, ":shell");
23 
24 system($cmd);

Zeile 22 fügt noch eine »quote()«-Runde für das SSH-Kommando hinzu und schließlich führt »system()« folgendes Kommando aus, das alle IP-Adressen von »somehost« einsammelt, ohne dort permanent ein Skript zu platzieren:

ssh -t somehost "perl -e " \$data = 
\`ifconfig\`; while(\$data =~ /in
et addr:(\\S+)/g) { print \"\$1\
\n\"; } ""

Man könnte natürlich genauso gut gleich das Ergebnis von »ifconfig« auf den lokalen Host holen und dort verarbeiten, aber bei größeren Datenmengen zeigen sich die Vorteile eines solchen mobilen Skripts.

Schlürf

Weil Skripte oft mit den gesamten Daten einer Datei arbeiten, soll es in Perl 6 künftig eine »slurp()«-Funktion geben. Sysadm::Install bietet schon heute eine entsprechende Funktion, zusammen mit dem Gegenstück »blurt()«, das gespeicherte Daten in einem Rutsch wieder in eine Datei zurückschreibt.

Ein Beispiel: Um Linux dazu zu bewegen, nach dem Booten nicht mehr automatisch in den grafischen X11-Modus zu wechseln, sondern den Login im Textmodus auszuführen, muss der Admin in »/etc/inittab« eine Zeile ändern. In »id:5:initdefault:« ersetzt er die »5« durch eine »3«. Mit »slurp()« und »blurt()« geht das ganz einfach, wie Listing 6 (Fixinittab) zeigt.

Listing 6:
Fixinittab

01 #!/usr/bin/perl
02 # fixinittab
03 
04 use Sysadm::Install qw(:all);
05 
06 $file = "/etc/inittab";
07 $data = slurp $file;
08 $data =~
09    s/id:5:initdefault:/id:3:initdefault:/;
10 blurt $data, $file;

Es geht sogar noch kompakter, wie Listing 7 belegt. Ähnlich Perls Inline-Edit-Modus (»perl -p -i -e “…”«) stellt Sys-adm::Install die »pie()«-Funktion bereit. Sie nimmt mindestens zwei Argumente: eine Referenz auf einen vom Benutzer definierten Callback und einen oder mehrere Dateinamen. »pie()« geht zeilenweise durch alle angegebenen Dateien, ruft für jede den Callback auf und ersetzt die Zeile durch den Rückgabewert der Funktion. Sind alle Änderungen ausgeführt, schreibt »pie()« das Ergebnis wieder in die Datei zurück.

Listing 7:
Fixinittab-pie

01 #!/usr/bin/perl
02 # fixinittab-pie
03 
04 use warnings;
05 use strict;
06 
07 use Sysadm::Install qw(:all);
08 
09 pie(sub {
10   s/id:5:initdefault
11    /id:3:initdefault/gx; $_;
12   }, "/etc/inittab");

Bei Ersetzungen mit dem Substitutionsoperator ist zu beachten, dass »s/a/b/« nicht etwa den Ergebnisstring zurückliefert, sondern die Anzahl der ausgeführten Ersetzungen. Besteht der Callback nur aus einer Ersetzung, sorgt »s/a/b/; ;« dafür, dass auch wirklich der Ergebniswert der Ersetzung zurück in die Datei wandert.

Tipp für Faule

Nicht für alle klingt das Klappern der Tastatur wie Musik: Wer es leid ist, ständig »#!/usr/bin/perl« zu tippen und »use Sysadm::Install qw(:all)«, der definiert sich einfach ein Vim-Makro wie in Abbildung 1. Es ordnet der Tastenkombination [!]+[P] (P steht für Perl) im Kommandomodus ein Insert-Kommando zu, gefolgt von den ersten sieben Zeilen eines Sysadm::Install-Skripts: der Perl-Shebang-Zeile, etwas Verzierung, einem Template für den Skriptnamen, dem Verwendungszweck und dem Namen des Autors. Das ».vimrc«-Kommando muss in einer Zeile stehen. Die später als »^M« angezeigten Zeilenumbrüche erreicht man mit [Ctrl]+[V] gefolgt von [Enter] im Vim-Eingabemodus.

Abbildung 1: Das Makro ordnet der Tastenkombination [!]+[P] im Kommandomodus die Startzeilen eines Perl-Skripts zu und setzt den Editor in den Einfügemodus.

Abbildung 1: Das Makro ordnet der Tastenkombination [!]+[P] im Kommandomodus die Startzeilen eines Perl-Skripts zu und setzt den Editor in den Einfügemodus.

Ein neues Skript entsteht einfach mit dem Aufruf »vim test-script«. Ein anschließendes [!]+[P] fügt den Header ein und setzt den »vim« in den Insert-modus (Abbildung 2). Ein aufregendes Perl-Shellskript kann beginnen! (jcb)

Abbildung 2: Der Erfolgt des Vim-Makros in Abbildung 1: Nach [!]+[P] kann der Skriptautor verzögerungsfrei drauflosskripten.

Abbildung 2: Der Erfolgt des Vim-Makros in Abbildung 1: Nach [!]+[P] kann der Skriptautor verzögerungsfrei drauflosskripten.

Infos

[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2005/02/Perl]

[2] Sysadm::Install: [http://search.cpan.org/~mschilli/Sysadm-Install-0.09/]

Der
Autor

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]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben