Die berühmten Perl-Einzeiler, das ist doch nur was für Gurus!

Naja. Was für ein Gesocks man nicht alles Guru nennt. ;-) Manchmal ist es einfach praktischer, ein sehr kleines Perl-Script zu einer einzelnen Zeile zusammenzufassen, die man dann mal schnell auf der Kommandozeile einfügen kann.

Beispiel: Zu einem Hostnamen die IP-Adresse ermitteln

Angenommen, man will zu einem Hostnamen die IP-Adresse rausfinden, und zwar nicht per DNS (wie es dig oder nslookup ausschließlich tun), sondern regulär so wie die Applikationen über den Resolver gehen, so daß /etc/hosts und ggf. NIS mit verwendet werden. Dann kann man natürlich ein Script schreiben:

use strict;

my $hostname = "www.linuxwiki.de";
my $address  = gethostbyname($hostname);
# Das Ergebnis von gethostbyname() auseinanderdröseln
my @bytes    = unpack('C4',$address);
# Mit Punkten zur IP-Adresse zusammensetzen
my $ip       = join(".", @bytes);

print "$ip\n";

exit();

Gähn. 5 Minuten editieren, chmod +x, und schon ist man reif für die Mittagspause.

Da mache ich mir dann gern die Mühe, einen solchen Fünfzeiler zu einem Einzeiler zusammenzufassen. Überall, wo Variablen verwendet werden, setzt man direkt den entsprechenden Aufruf ein. Schon hat man das kleine Lookup-Progrämmchen von oben wie folgt komprimiert und kann es als Argument beim einem Aufruf von Perl mitgeben:

# IP-Adresse aus Hostname ermitteln
perl -e 'print join(".",unpack('C4',scalar(gethostbyname($ARGV[0]))))."\n"' www.linuxwiki.de

Dieses Machwerk fügt man schnell per Copy & Paste am Prompt ein, und fertig. Ich finde sowas praktisch und habe noch mehr davon gesammelt (u.a. mußte ich mal einen kleinen Portscanner schreiben, weil ich das "Hackertool" nmap nicht benutzen durfte), die ich oft einsetze, und die ich hier Stück für Stück vorstellen (und für mich griffbereit haben) möchte. Dieses "Fragment" von heute soll mal nur ein Anfang sein.

Falls jemand noch andere Perl-Einzeiler aus dem echten Leben beisteuern könnte, wäre das super. Zum Einzeiler auch noch eine kleine Langversion wäre perfekt.

Zu einer IP-Adresse den Hostnamen ermitteln

Das Gegenstück zum obigen Beispiel. Klingt wieder banal, kann aber wieder dann sinnvoll sein, wenn außer DNS noch andere Methoden der Namensauflösung am Start sind (z.B. /etc/hosts oder NIS).

Erst nochmal die ausführliche Version:

use strict;

my $ip       = "204.152.189.116";
# IP in einzelne Bytes splitten
my @bytes    = split /\./, $ip;
# Gesplitte IP-Adresse für gethostbyaddr() aufbereiten
my $address  = pack('C4',@bytes);
my $hostname = gethostbyaddr($address,2);

print "$hostname\n";

Und zusammengefaßt, um es von irgendwo schnell heranzukopieren:

perl -e 'print gethostbyaddr((pack('C4',(split /\./,$ARGV[0]))),2)."\n"' 204.152.189.116

Spielereien mit der Uhrzeit

Jetzt wirds etwas einfacher.

Man bekommt z.B. aus einem Logfile einen Unix-Zeitstempel (nach dem Muster 1001602799) und will nun wissen, welches Datum und Uhrzeit das war.

perl -e 'print localtime(1001602799)."\n"'

Eine solche Konstruktion kann man z.B. in einem Shell-Script einsetzen.

Aber vorsicht: Dadurch, daß Perl immer neu gestartet und das "Programm" neu übersetzt werden muß, ist das für große Mengen von Umwandlungen keine gute Lösung, denn der Ablauf würde sehr langsam. Wenn es also um große Datenmengen geht, sollte besser eine komplette Schleife in Perl darum konstruiert werden.

Immer wieder gern gefragt auf Unix-Mailinglisten: Wie findet man heraus, welches Datum gestern war?

Ganz einfach: Man zieht vom aktuellen Unix-Zeitstempel (siehe oben), den man mit time() ermittelt, den Wert für einen Tag (ein Tag hat 86400 Sekunden) ab, und wandelt diese Zahl in ein lesbares Datum um.

Um also das obige Beispiel anzupassen:

perl -e 'print localtime(time()-86400)."\n"'

So richtig schön ist das aber nicht, denn diese Lösung zeigt nicht das gestrige Datum an, sondern Datum und Uhrzeit von vor 24 Stunden. Also formatieren wir das Ergebnis nochmal um, so daß nur Tag, Monat und Jahr angezeigt werden. Dazu brauchen wir die Funktion strftime() aus dem POSIX-Modul von Perl, das standardmäßig in der Perl-Distribution enthalten ist. Dieses wird beim Aufruf des Einzeiler mit der Option -M dazugeladen:

perl -MPOSIX -e 'print strftime("%d.%m.%Y", localtime(time()-86400))."\n"'

Hier nochmal eine Langversion davon:

use strict;
use POSIX;

my $heute         = time();
my $gestern       = $heute - 86400;
# localtime() ergibt im Array-Kontext ein Array mit den Datumsfeldern
my @gestern_local = localtime($gestern);
# strftime() erwartet als Übergabewert ein Array
my $gestern_datum = strftime("%d.%m.%Y", @gestern_local);

print "$gestern_datum\n";

exit();

Automatische Schleifen für Ein- und Ausgabe mit -p und -n

Wird beim Aufruf von Perl die Option -n angegeben, so bastelt Perl die folgende Schleife um das Script:

LINE:
while(<>){
      ... (Hier die Befehle)
}

Nehmen wir mal an, es soll ein Script geschrieben werden, das eine ROT13-Codierung durchführt:

use strict;
while (<>){
    tr/[a-z][A-Z]/[n-za-m][N-ZA-M]/;
    print;
}

Das läßt sich dank perl -n wie folgt zusammenfassen:

perl -n -e 'tr/[a-z][A-Z]/[n-za-m][N-ZA-M]/;print;'

Und dies wiederum läßt sich nochmals durch die Option -p kürzen. Die Option -p veranlaßt Perl dazu, die Schleife aus dem obigen Kasten wie folgt zu erweitern:

LINE:
while(<>){
      ... (Hier die Befehle)
}continue{
    print;
}

Es erfolgt also am Ende der while-Schleife die Ausgabe der in $_ gepufferten Daten. Damit läßt sich beim vorher genannten Einzeiler ein weiterer Befehl einsparen:

perl -p -e 'tr/[a-z][A-Z]/[n-za-m][N-ZA-M]/'

Auf diese Weise ist z.B. immer ein sed-Ersatz zur Hand, der die erweiterten RegularExpressions von Perl versteht. Insbesondere beim Matching von Mustern, die über mehrere Zeilen gehen, kann das deutlich einfacher sein, als das "Original" sed.

# Sehr einfaches Beispiel für Perl als sed-Ersatz
perl -p 's/foo/bar/g' <infile >outfile

ToDo: Hier muß eigentlich unbedingt was über das inplace-Editing mittels -i geschrieben werden.

Die Autosplit-Optionen -a und -F

Diese Optionen funktionieren nur im Zusammenhang mit -p oder -n.

Wer schon einmal mit awk gearbeitet hat, kennt vielleicht das Prinzip des automatischen Splittens von Zeilen an bestimmten Trennzeichen. Wenn man in Perl nun z.B. eine Liste der Nutzer auf einem System (aus /etc/passwd) ausgeben will, könnte man das z.B. folgendermaßen tun:

use strict;
open my $passwd_fh, "</etc/passwd" or die "Kann /etc/passwd nicht öffnen: $!\n";
while (<$passwd_fh>){
    my ($user, $pwd, $uid, $gid, $gecos, $home, $shell) = split /:/;
    print "$user\n";
}
close $passwd_fh;

Gibt man die Option -a an, wird die Zeile standardmäßig an den Leerzeichen gesplittet und automatisch ein Array F angelegt, in dem das Ergebnis des Split-Vorgangs abgelegt wird. Will man nicht am Leerzeichen splitten, kann man mittels -F ein Trennzeichen angeben, an dem gesplittet werden soll, z.B.: -F:

Das o.g. Beispiel könnte unter Verwendung des Autosplit-Modus folgendermaßen geschrieben werden:

perl -a -F: -n -e 'print"@F[0]\n"' < /etc/passwd

Das ganze erweitert, um die Realname der Nutzer ("Gecos-Feld") auszugeben, die /bin/bash als ihre Login-Shell haben:

perl -a -F: -n -e '$F[6]=~/\/bin\/bash/&&print$F[4]."\n"' < /etc/passwd

Das letzte Beispiel noch einmal in ausführlicher Form:

{{{
#!/usr/bin/perl -w
use strict;
open my $passwd_fh, "</etc/passwd" or die "Kann /etc/passwd nicht öffnen: $!\n";
while (<$passwd_fh>){
    my ($user, $pwd, $uid, $gid, $gecos, $home, $shell) = split /:/;
    if ($shell =~ /\/bin\/bash/){
        print "$gecos\n";
    }
}
close $passwd_fh;

Filtern von URLs aus Textdateien

So, jetzt kommt Der Hammer(tm): Ein Perl-Skript zum Filtern von Dateinamen aus Hyperlinks. Mal sehen wer hier durchsteigt :-)

 $url = "$1\n" while m{ < \s* A \s+ HREF \s* = \s* "([[^"\ ]*)" \s* > }gsix;

Hinweise:

Ohne viel Worte

Programmierhilfen

Die folgenden Beispiele arbeiten auf Dateien mit Konstantendefinitionen (Pascal, Java, C++), eine je Zeile, Format <name> = <zahl>;.

public const int AAA = 104;
// Beliebige Zeile ohne Konstante
public const int BBB = 103;
public const int ZZZ = 1000;

perl -pi -e '$ii++ if s/=\s+\d+;/"= " . ($ii+0) . ";"/e;' Const.java

perl -p -e '$n=99 if ! $n; $n++ if s/=\s+[-+*\/\w\s]+;/= $n;/;' const.hpp >const2.hpp

perl -pi -e '$n=99+1 if !$n && /\bAAA\s*=/; $n++ if $n>0 && s/=\s+\d+;/"= " . ($n-1) . ";"/e;$n=-1 if $n>0 && /\bBBB\s*=/;' main.pas

Work in Progress

Weitere Schnipsel werden mit der Zeit noch ergänzt.

PerlEinzeiler (zuletzt geändert am 2007-12-23 22:48:34 durch localhost)