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"'
Was ist eigentlich an date -dyesterday +%Y-%m-%d so schlecht, als daß man gleich den Perl-Vorschlaghammer ziehen muß?
Das schlechte daran ist, daß es auf nicht-GNU-Systemen nicht funktioniert. Das betrifft nicht nur Kommerzunixe, sondern auch die BSDs als Freie Systeme, (jedenfalls mindestens mal das FreeBSD 3.4, das ich noch laufen habe). Und wir sind ja keine Linux-Fachidioten, sondern können uns in jedem Unix-ähnlichen Umfeld elegant bewegen, gell? -- MartinSchmitt 2002-11-21 21:04:12
Nur der Vollstaendigkeit halber, unter FreeBSD geht das so: date -v-1d +%Y-%m-%d -- DanijelTasov 2003-11-28 12:33:35
MacOSX (ja, BSD):
[Benny-Siegerts-Computer:~] bsiegert% date -dyesterday +%Y-%m-%d date: illegal option -- d usage: date [-nu] [-r seconds] [+format] date [[[[[cc]yy]mm]dd]hh]mm[.ss]
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:
In Perl ist while (bedingung) {kommando;} und kommando while(bedingung) funktionell identisch.
m{...}gsix; umschließt den Suchausdruck. Dabei heißt m "Suche auch über mehrere Zeilen hinweg". Sonst gehts zeilenweise.
\s* = "Beliebig viele Leerzeichen (Tab, Space, etc.)"
\s+ = "Null oder ein Leerzeichen (s.o.)"
(...) Der (erste) Ausdruck in runden Klammern geht als $1 ans aufrufende Programm bzw. Variable zurück.
[[...] kennzeichnet (einbuchstabige) Alternativen.
'' innerhalb von ''[[..]'' kennzeichnet eine Negation, d.h. alles außer den folgenden Alternativen. Außerhalb von Eckklammern kennzeichnet '' einen Zeilenanfang.
\ Das Leerzeichen muß hier "festgenagelt" werden, damit es als Character und nicht als Leerraum zwischen Ausdrücken erkannt wird.
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
- Neunummerieren aller Konstanten, beginnend bei 0.
- Nur Konstanten mit Zahlwerten werden ersetzt.
- Eingabedatei wird ersetzt!
perl -p -e '$n=99 if ! $n; $n++ if s/=\s+[-+*\/\w\s]+;/= $n;/;' const.hpp >const2.hpp
- Neunummerieren von Konstanten, beginnend bei 99.
Auch Konstanten mit Ausdrücken werden ersetzt: Bsp BBB = AAA*2+1;
- Ausgabe nach stdout.
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
- Neunummerieren aller Konstanten mit Zahlwerten, die zwischen AAA und BBB (beide eingeschlossen) stehen, beginnend mit 99.
- Statt 99 geht auch 0, aber nicht kleiner!
- Eingabedatei wird ersetzt!
Work in Progress
Weitere Schnipsel werden mit der Zeit noch ergänzt.