Für kurze administrative Aufgaben basteln Systemadministratoren oft Shell-Skripts. Später rennen sie dann gegen Mauern, weil der Sprachumfang nicht für Erweiterungen ausreicht. Warum nicht gleich richtig anfangen?
Mit ein paar Shell-Kommandos lassen sich schnell Skripte zur
Systemadministration zusammenklopfen: Eine Installationsroutine mit
cp
, mv
und chmod
hier, eine mit grep
, awk
und sed
gezimmerte Abfrage dort. Kommen dann weitere Anforderungen, werden
die Skripts komplizierter, oft unübersichtlich, und manchmal
endet der Skripter in einer Sackgasse: Leider gehen manche
Sachen in der Shell halt nicht oder nur sehr umständlich.
Kreative Programmierer finden zwar immer obskure Methoden, Mauern zu
überwinden und Shells wie bash
, ksh
und tcsh
bieten
manche Vorzüge einer ``echten'' Programmiersprache, doch warum
zu Fuß gehen, wenn man fahren kann?
Freilich ist Standard-Perl für viele einfache Aufgaben nichts für faule Tipper: Wer will schon
open FILE, "<filename" or die "Cannot open filename ($!)";
schreiben wenn's in der Shell ein einfaches
cat filename
tut? Sysadm::Install
, ein neues Modul vom CPAN schafft hier Abhilfe.
Es exportiert Funktionen wie cp
, mv
, untar
, mkd
,
rmf
(rm -f), cd
, damit Shell-Skripter sich in Perl zuhause
fühlen.
Weiter bietet es Funktionen zu interaktiven Benutzerabfragen, zur Dateimanipulation, zum URL-Download und natürlich vereinfachte Schnittstellen zum Aufruf externer Programme.
topng
zeigt ein Skript, das eine Reihe angegebener JPG-Bilder
mit Hilfe der 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, und 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 Standard-Funktionen 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.
Shell-Programmierer, die bislang immer
cp a b || exit 1 mv c d || exit 1
schreiben mussten, atmen 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.
tar
ist für viele, die es nicht täglich verwenden, ein Buch
mit sieben Siegeln. War nun xf
die Option zum Entpacken oder cf
?
Und dann gibt es immer noch Unbelehrbare, die kein einzelnes
Top-Verzeichnis einpacken, sondern auf oberster Ebene alle möglichen
Dateien hineinpfeffern, auf dass der Entpackende sofort sein
aktuelles Verzeichnis vollkleistert. 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 in Listing
untar
wird einfach mit dem Tarballnamen aufgerufen:
untar pari-2.1.4.tgz
Wer statt stoischer Ruhe lieber Informationen über den
Ablauf wünscht, fügt die Option -v
hinzu
untar -v pari-2.1.4.tgz
und erhält Details über die Transaktionen:
2004/12/04 00:24:43 untar pari-2.1.4.tgz 2004/12/04 00:24:43 Sniffing archive 'pari-2.1.4.tgz' 2004/12/04 00:24:43 dir=pari-2.1.4 2004/12/04 00:24:43 archdir=pari-2.1.4 2004/12/04 00:24:43 Return pari-2.1.4 pari-2.1.4 2004/12/04 00:24:43 Nice archive, extracting to subdir pari-2.1.4 2004/12/04 00:24:43 rmf pari-2.1.4
01 #!/usr/bin/perl 02 ########################################### 03 # untar -- Untar tarballs 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Log::Log4perl qw(:easy); 10 use Getopt::Std; 11 use Sysadm::Install qw(untar); 12 13 getopts('v', \my %opts); 14 15 Log::Log4perl->easy_init( 16 $opts{v} ? $DEBUG : $ERROR); 17 18 for my $tar (@ARGV) { 19 untar($tar); 20 }
Manches Installationsskript fordert den Benutzer zu interaktiven
Eingaben auf. Meist
reicht es, auf die Enter-Taste zu hämmern, und das ist ganau, 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 schon werden tausend Fragen gestellt. Wer sicher ist, dass
auf alle die voreingestellte Antwort passt, gibt entweder die
Option -d
an oder beginnt, auf die Eingabetaste zu hämmern.
Listing mkperl
lädt den aktuellen stabilen Perl-Tarball mit
download()
von perl.com,
entpackt ihn mit untar()
,
konfiguriert den Release und startet den Build. Es nutzt
die -d
-Option von Configure
, da man bei der Perl-Konfiguration nicht
immer alles durchgehämmern kann, aber setzt hammer()
ein, damit
der letzte Prompt, der den Benutzer fragt, ob er die angegebene
Konfigurationsdatei manuell ändern will, automatisch abgehakt wird.
01 #!/usr/bin/perl 02 ########################################### 03 # mkperl - Download the latest stable perl, 04 # configure and install it. 05 # Mike Schilli, 2004 (m@perlmeister.com) 06 ########################################### 07 use strict; 08 use warnings; 09 10 use Log::Log4perl qw(:easy); 11 Log::Log4perl->easy_init($DEBUG); 12 use Sysadm::Install qw(download 13 hammer untar cd sysrun); 14 15 download "http://www.perl.com/" . 16 "CPAN/src/stable.tar.gz"; 17 untar "stable.tar.gz"; 18 cd "stable"; 19 hammer("./Configure", "-d", "-D", 20 "prefix=/home/mschilli/PERL-test"); 21 sysrun("make install");
Manchmal brauchen Skripts eine Bestätigung vom Benutzer: Kann dieser
Default übernommen werden, 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 statt dessen ein neu Eingegebener. pick
stellt eine Reihe von
Optionen zur Auswahl, numeriert sie durch, und lässt den Benutzer
die gewählte Nummer eingeben.
Das Skript in Listing
input
zeigt an einem Beispiel, wie erst ein Textstring eingeholt
und dann eine von drei Optionen gewählt wird:
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 eingegebenen
bzw. ausgewählten Wert, "Bill Gates"
und ``300K-''.
01 #!/usr/bin/perl 02 ########################################### 03 # input -- Test ask() and pick() 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Sysadm::Install qw(:all); 10 11 my $name = ask "Name", "No-Name-Entered"; 12 print " Name: $name\n"; 13 14 my $salary = pick "Salary", 15 ["0-100K", "100K-200K", "300K-"], 1; 16 print " Salary: $salary\n";
Wer Perls Einzeiler einsetzt, kennt das Problem, den Perl Code auf der Kommandozeile vor der gefräßigen Shell zu schützen:
perl -e "print "Hi!\n""
wird nicht funktionieren, da die inneren Anführungszeichen und der Backslash von der Shell gefressen werden und das Ausrufezeichen vorher ausgeführte Kommandos zurückholt.
Maskiert man die empfindlichen Zeichen mit einem Backslash (und den Backslash mit einem weiteren Backslash), klappt's:
perl -e "print \"Hi\!\\n\""
Eine weiter Möglichkeit sind einfache Anführungszeichen, aber dann interpoliert die Shell keine Variablen mehr und einfache Anführungszeichen im Code müssen maskiert werden. Und was passiert, wenn man obiges Kommando nicht auf dem lokalen Rechner ausführen, sondern mit
ssh -t localhost command
auf einer anderen Maschine irgendwo im Netz? Dann muss jedes Sonderzeichen (und auch die vorher maskierten Sonderzeichen und ihre Maskierer) wiederum maskiert werden. Und schon stellt sich das heimelige Gefühl der Backslashitis ein:
ssh -t somehost "perl -e \"print \\\"Hi\\\!\\\\n\\\"\""
Wer derlei Gehirnakrobatik scheut, zieht einfach die von Sysadm::Install
auf Verlangen exportierte Funktion qquote()
heran. Sie klatscht
doppelte Anführungszeichen um einen ihr übergebenen String und
maskiert im String enthaltene Quotes und Backslashes. Ist der
zweite ihr übergebene Parameter :shell
, genießen auch der
gefährdete Dollar, das bedrohte Ausrufezeichen und die hinterhältige
Backquote diesen Schutz.
Listing ips
definiert ab Zeile 11 ein Skript, das das Kommando
ifconfig
ausführt und die IP-Adressen aller angezeigten
Netzwerk-Interfaces extrahiert. Zeile 18 entfernt Zeilenumbrüche und
überflüssige Leerzeichen aus dem Skripttext, und qquote()
in Zeile 21
formt daraus einen kompakten, in doppelte Quotes eingeschlossenen String,
der hinter perl -e
gehängt wird.
Zeile 23 hängt noch eine quote()
-Runde
für das ssh-Kommando dran 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 =~ /inet 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.
01 #!/usr/bin/perl 02 ########################################### 03 # ips -- run a script on a remote machine 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Sysadm::Install qw(qquote); 10 11 my $script = q{ 12 $data = `ifconfig`; 13 while($data =~ /inet addr:(\S+)/g) { 14 print "$1\n"; 15 } 16 }; 17 18 $script =~ s/\s+/ /g; 19 20 my $cmd = "perl -e " . 21 qquote($script, ":shell"); 22 23 $cmd = "ssh -t somehost " . 24 qquote($cmd, ":shell"); 25 26 system($cmd);
Weil Skripts oft mit den gesamten Daten einer Datei arbeiten, soll
es in Perl 6 zukünftig eine slurp()
-Funktion geben. Sysadm::Install
bietet schon heute eine an, zusammen mit dem Gegenstück blurt()
,
das gespeicherte Daten wieder in einem Rutsch in eine Datei zurückschreibt.
Ein Beispiel: Um zum Linux dazu zu bewegen, nach dem Booten nicht mehr
automatisch in den graphischen X-Modus zu wechseln, sondern den Login
im Text-Modus durchzuführen, muss in /etc/inittab
die Zeile
id:5:initdefault:
durch
id:3:initdefault:
ersetzt werden. Mit slurp()
und blurt()
geht das ganz einfach,
wie in Listing fixinittab
gezeigt.
01 #!/usr/bin/perl 02 ########################################### 03 # fixinittab 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 07 use Sysadm::Install qw(:all); 08 09 $file = "/etc/inittab"; 10 $data = slurp $file; 11 $data =~ 12 s/id:5:initdefault:/id:3:initdefault:/; 13 blurt $data, $file;
Aber es geht sogar noch kompakter, wie Listing fixinittab-pie
zeigt:
In Anlehnung an perls Inline-Edit-Modus, der mit
perl -p -i -e "..."
aktiviert wird, stellt Sysadm::Install
die pie()
-Funktion bereit,
die mindestens zwei Argumente nimmt: Eine Referenz auf einen vom
Benutzer definierten Callback und ein 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 durchgeführt, schreibt pie()
das
Ergebnis wieder in die originale Datei zurück.
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.
01 #!/usr/bin/perl 02 ########################################### 03 # fixinittab-pie 04 # Mike Schilli, 2004 (m@perlmeister.com) 05 ########################################### 06 use warnings; 07 use strict; 08 09 use Sysadm::Install qw(:all); 10 11 pie(sub { 12 s/id:5:initdefault 13 /id:3:initdefault/gx; $_; 14 }, "/etc/inittab");
Zum Schluss noch ein Tipp für ganz Faule: Wer es leid ist, ständig
#!/usr/bin/perl
zu tippen und use Sysadm::Install qw(:all)
, der
definiert sich einfach ein vim
-Macro wie in Abbildung 1 gezeigt.
Es ordnet der Tastenfolge !P
(P 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,
und einem Template für den Skriptnamen, Verwendungszweck und den
Namen des Autors. Das .vimrc
-Kommando muss in einer Zeile stehen,
die später als ^M
angezeigten Zeilenumbrüche erreicht man mit
CTRL-V, Return in vi
s Eingabemodus.
Ein neues Skript entsteht so einfach mit dem Aufruf vi test-script
. Ein
anschließendes !P
fügt den Header ein und setzt den vi
in
den Insert-Modus. Ein neues, aufregendes Perl-Shell-Skript kann beginnen!
Abbildung 1: Ein vim-Macro, das der Tastenfolge !P im Kommando-Modus die Startzeilen eines Perl-Skripts zuordnet und den Editor in den Einfüge-Modus setzt. |
Abbildung 2: Nachdem !P eingegeben wurde: Der Skriptautor kann verzögerungsfrei loslegen. |
Michael Schilliarbeitet 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. |