Wer viel in der Shell arbeitet und navigiert, nach Textstücken sucht oder CPAN-Module installiert, weiß die hier vorgestellten Helferscripts sicher als Fingerschoner zu schätzen.
Neulich zog ich auf einen neuen Entwicklungs-Desktop um und packte die Gelegenheit beim Schopf, mein überquellendes Home-Verzeichnis nicht nur aufzuräumen, sondern neu aufzubauen. Dort hatten sich über die Jahre hunderte von teilweise schon wieder obsoleten Helferskripts angesammelt. Um Ordnungs in das Chaos zu bringen, beschloss ich, wirklich bei Null anzufangen und jedes bei der täglichen Tipparbeit unsäglich vermisste Skript nachzuinstallieren -- in reproduzierbarer Art und Weise, versteht sich, auf dass der nächste Umzug ohne Gefluche vonstatten gehe.
Alle Skripts liegen nun in Unterverzeichnissen
verschiedener git-Repositories.
Damit der Benutzer die Helferlein ohne Pfadangabe
aufrufen kann, zeigen Symlinks zeigt vom bin-Pfad des Home-Verzeichnisses
zu den eigentlichen
Skripts. Ein weiteres Skript, binlinks
, ordnet in seinem
DATA-Bereich den ins git-Repository eingecheckten Skripts Links im
lokalen bin-Verzeichnis des Users zu. So verbleibt zum Beispiel das Skript
logtemp
zum Auslesen des in [2] vorgestellten Temperaturfühlers
im Git-Repo "articles", während das handgeschriebene
Konvertierwerkzeug cvs2git
im Schilli-Labs-Repo sandbox
aufgehoben
ist.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Log::Log4perl qw(:easy); 04 use File::Basename; 05 use Sysadm::Install qw(mkd); 06 07 Log::Log4perl->easy_init($DEBUG); 08 09 my ($home) = glob "~"; 10 my $home_bin = "$home/bin"; 11 12 while (<DATA>) { 13 chomp; 14 15 my ($linkbase, $src) = split ' ', $_; 16 17 $src = "$home/$src"; 18 my $binpath = "$home_bin/$linkbase"; 19 20 if (-l $binpath) { 21 DEBUG "$binpath already exists"; 22 next; 23 } elsif (-e $binpath) { 24 ERROR "$binpath already exists, ", 25 "but not a link!"; 26 next; 27 } 28 29 INFO "Linking $binpath -> $src"; 30 31 symlink $src, $binpath 32 or LOGDIE 33 "Cannot link $binpath->$src ($!)"; 34 } 35 36 __DATA__ 37 logtemp git/articles/temper/eg/logtemp 38 cvs2git git/sandbox/cvs2git/cvs2git
Kommt ein neues Skript in den bin-Bereich hinzu, trägt der
Entwickler es ans Ende des DATA-Bereichs von binlinks
ein und ruft
letzteres auf. Es klappert dann alle Einträge ab, verifiziert, ob der
gewünschte Link in ~/bin
schon existiert und legt ihn an, falls dies
noch nicht der Fall ist. Dass
binlinks
selbst wiederum in einem Git-Repo liegt, sollte auf der Hand
liegen. Es nutzt das Modul Sysadm::Install vom CPAN, einzig um dessen
Funktion mkd
willen, die neue Verzeichnisse ohne Murren anlegt und
mit Log4perl-Ausgaben zum Ablauf Auskunft gibt.
So handelt es sich bei einem aufgerufenen Skript oft um einen Symlink.
Wenn ein Symlink zu einer Datei in einem anderen Verzeichnis zeigt,
möchten Entwickler oft mit cd dorthin wechseln.
Dies erledigt das Kommando lcd
mit dem Link als
Parameter (Abbildung 1).
Abbildung 1: Die Funktion lcd wechselt in das Verzeichnis, das das Skript enthält, auf das ein Symlink zeigt. |
Alte Unix-Hasen wissen natürlich, dass ein Skript
nicht das aktuelle Verzeichnis des Aufrufers wechseln kann. Skripts
laufen in einer Subshell ab, und bei deren Beendigung sind für den Aufrufer
keine nachhaltigen
Nebenwirkungen bemerkbar. Aus diesem Grund ist lcd
im
Startskript .bashrc
der Bash-Shell als Bash-Funktion definiert:
function lcd () { cd `symlinkdir $1`; \ pwd; ls; }
Ruft jemand lcd bin/cvs2git
auf, übergibt die Bash-Funktion
das Argument bin/cvs2git
an das Skript symlinkdir
und ruft das
Shell-Kommando
cd
mit dessen Ausgabe auf. Die Implementierung von symlinkdir
zeigt
Listing 2.
01 #!/usr/local/bin/perl -w 02 use strict; 03 use File::Basename; 04 05 my($link) = @ARGV; 06 07 die "No link specified" unless $link; 08 die "$link not a symbolic link" 09 unless -l $link; 10 11 while(-l $link) { 12 $link = readlink($link); 13 } 14 15 $link = dirname($link) unless -d $link; 16 print "$link\n";
Das Skript folgt mit der Systemfunktion readlink()
dem als Parameter überreichten Link und wiederholt dies solange, bis
das Ergebnis kein Link mehr ist. Die Funktion dirname()
aus dem
Modul File::Basename
extrahiert aus dem resultierenden Pfad das
Verzeichnis und Zeile 16 gibt es auf der Standard-Ausgabe aus, wo
die Bash-Funktion lcd()
es aufschnappt, dorthin wechselt, es mit
pwd
ausgibt und mit ls
die dort liegenden Einträge auflistet.
Kaum ein Perl-Snapshot kommt ohne zusätzlich zu installierende
CPAN-Module aus, und üblicherweise klappt dies kurz und schmerzlos
mit einer CPAN-Shell, die entweder als perl -MCPAN -eshell
aufgerufen
wird, oder mittels dem neueren Perl-Distributionen beiliegenden
Skript cpan
.
Allerdings führt der Resourcenhunger der CPAN-Shell auf Billighost-Accounts schnell zum Rauswurf. Abbildung 2 zeigt, was nach einigen Sekunden auf dem Shared-Hosting-Provider Dreamhost passiert, ohne dass die CPAN-Shell auch nur das gewünschte Modul vom CPAN geladen hätte. Angeblich verbraucht es zuviel RAM-Speicher und damit die anderen Shared-Accounts nicht leiden, zieht Dreamhost - wohl etwas übereilt - die Notbremse.
Abbildung 2: Zuviel für den Billighoster: Eine CPAN-Shell zum Installieren eines Perl-Modules führt zum Ziehen der Notbremse. |
Abbildung 3: Das resourcenschonende CPAN-Modul App::cpanminus installiert mit cpanm problemlos das gewünschte Modul. |
Rettung naht in Form des CPAN-Moduls App::cpanminus. Abbildung 3 zeigt die kurz gehaltene Ausgabe des unscheinbaren Tausendsassas, der so wenig Resourcen verbraucht, dass es selbst dem Billighoster mit seinen strengen Richtlinien nicht auffällt.
Wie sein großer Bruder CPAN.pm weiß auch cpanminus
mit lokalen
Modulpfaden umzugehen. Damit der User CPAN-Module mit einem
nicht-priviligierten Account installieren kann und auch nicht die
heilige Ordnung des Paketmanagers durcheinander bringt, raten
Experten dringend zur Verwendung von local::lib
, falls
die verwendete Linux-Distribution benötigte Perlmodule nicht
im Repository führt.
Das Modul local::lib installiert der Admin (ein letztes Mal als root) am geschicktesten mittels des Paketmanagers, unter Ubuntu zum Beispiel mit
sudo apt-get install liblocal-lib-perl
Erlaubt ein Hoster keinen Root-Zugriff und lässt sich auch nicht erbarmen, das nützliche local::lib per Admin-Eingriff nachzuinstallieren, lädt der Benutzer den Tarball selbst vom CPAN, entpackt ihn und ruft
perl Makefile.PL --bootstrap make install
auf. Künftig mit einer CPAN-Shell installierte Module landen dann im Verzeichnis "perl5" unter dem Heimverzeichnis des unpriviligierten Users. Wenn dieser anschließend noch das Kommando
eval $(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)
in die Startup-Datei seiner Bash-Shell anhängt normalerweise
(.bashrc
), setzt es die Variablen
PERL_MM_OPT und PERL5LIB, sodass auf der einen Seite
sowohl die CPAN-Shell als auch cpanminus bei 'make install' neu
installierte Module lokal im Home-Verzeichnis installieren und andererseits
aufgerufene Skripts, die die neu installierten Module mit "use XXX" einbinden,
diese auch finden. Falls diese (zum Beispiel als Cronjob) nicht über
die Environment-Variablen aus .bashrc verfügen, tut's auch ein explizit
eingetragenes use local::lib
im Programmcode vor dem Laden der
benötigten lokal installierten Module.
Oft weiß der Benutzer, dass sich unterhalb des aktuellen Pfades irgendwo eine Datei befindet, in der sich ein gesuchter Textstring "blabla" befindet. In der Shell könnte man nun einen find-Befehl wie etwa
find . -type file -exec grep blabla {} /dev/null \;
absetzen, aber das ist extrem viel Tipparbeit und erfordert einiges
Nachdenken, speziell wegen des Tricks mit /dev/null
, das bei
Einzeltreffern auch den Dateinamen anzeigt und dem seltsamerweise
erforderlichen maskierten Semicolon, der der Option -exec
anzeigt,
dass das ihr übergebene Kommando damit endet. Früher hatte ich ein
Skript findgrep
, das mit Perls File::Find
-Modul eine rekursive
Textsuche startete, aber seit es ack
[3] gibt, lädt man das mächtige
Kommando einfach vom CPAN und tippt
$ ack blabla
ein -- fertig ist der Lack. Allerdings ist das Skript sehr penibel mit
Dateitypen: Nur was es aufgrund der Namensendung als textähnliche
Datei ansieht, untersucht es auch. Möchte man alle Dateien durchforsten,
muss man ack -a blabla
eingeben.
Wer gesteigerten Wert auf Performance legt, ist in einem Git-Repository mit der oft übersehenen Funktion
$ git grep blabla
übrigens weit besser bedient. Da git
die von ihm verwalteten Dateien
in einem Index abspeichert, braucht es für die rekursive Suche
keine Dateibäume zu durchforsten und schlägt den ungeschlachten
Ansatz gerade bei Dateien um Längen, die noch nicht im Buffer Cache des
Operationssystems liegen und in tief verschachtelten Ordnern ausharren.
Damit selbstgeschriebene Perlskripts vorgegebenen Normen genügen, schickt
der ordnungsliebende Programmierer sie am Ende durch den Beautifyer
perltidy
. Dieses Skript ist als CPAN-Modul erhältlich und versteht eine
Fülle von Konfigurationen, die jedem Stil gerecht werden. Wo stehen die
geschweiften Klammern, in der if-Zeile oder in der nächsten? Kommt
das else
direkt nach der schließenden geschweiften Klammer oder erst
nach einem Zeilenumbruch? Leerzeichen zwischen runden Klammern bei
Funktionsaufrufen und deren Argumenten? Und, ganz wichtig, wie groß
ist die maximale Zeilenlänge, ab wann muss der Formatierer lange Codezeilen
umbrechen?
Die Manualseite von perltidy
listet alle Optionen
auf und beschreibt deren Wirkung. Listing 3 zeigt die Konfiguration
für Perl-Listings im Linux-Magazin. Die Zeilenbreite beträgt 43 Zeichen,
und Zeilen in Blöcken rückt der Formatierer um zwei Zeichen ein
(-i=2). Bricht er eine Zeile um, rückt er den Zeilenrest auf
der nächsten Zeile ebenfalls um zwei Zeichen ein (-ci=2).
Else-Anweisungen folgen direkt, ohne Zeilenumbruch, nach der schließenden
geschweiften Klammer des if-Blocks ("cuddled else", -ce). Und da
Platz im Magazin stets Mangelware ist und die Redakteure gern
damit geizen, steht die "vertical tightness", also die vertikale Textdichte,
auf dem höchsten Wert -vt=2
. Mit dieser Option geizt der Formatierer
mit Zeilenumbrüchen wie's nur geht. Ebenfalls um Platz zu sparen
bestimmt -nbbc
schließlich, dass vor ganzzeiligen Kommentaren
keine Leerzeilen stehen.
1 # perltidy-Optionen für Perlskripts im Linux-Magazin 2 3 -l=43 # line width 4 -i=2 # 2 cols indent 5 -ci=2 # 2 cols continuation indent 6 -ce # cuddled else 7 -vt=2 # vertical tightness 8 -nbbc # no blank lines before whole-line comments
Damit der Formatierer ein Perlskript mit den definierten Optionen ummodelt, ruft der Entwickler ihn mit
perltidy -pro=pfad/perltidyrc scriptname
auf, worauf er, falls das Skript syntaktisch korrekt ist, die Datei
scriptname.tdy
erzeugt, die entsprechend umformatiert wurde. Wer
möchte, definiert mit
:nnoremap <buffer> <silent> X :w<Enter>1GdG\ :.!perltidy -pro=pfad/perltidyrc <%<Enter>
noch ein vim-Kommando, das die Formatierung im Editor vornimmt, falls der Benutzer "X" drückt. Bequemer geht's nun wirklich nicht mehr.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2011/09/Perl
"Klimaforschung", Michael Schilli, Linux Magazin 10/2010, http://www.linux-magazin.de/Heft-Abo/Ausgaben/2010/10/Klimaforschung
"ack", http://betterthangrep.com
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. |