[LinuxOB] CSV - Datei
Daniel Dombrowski
daniel.dombrowski at linuxob.de
So Sep 11 14:24:09 CEST 2005
On 2005.09.11 13:26, Michael Gisbers wrote:
> Am Mittwoch, 7. September 2005 19:23 schrieb Daniel Dombrowski:
[...]
> > Auch wenn es bestimmt kürzer geht, so geht es auf jeden Fall auch
> > so:
> >
> > perl -e 'while(<>) { if ($_ =~ s/^\///) {chomp $l} print $l; $l =
> > $_} print $l'
> >
> > Datei auf STDIN reinschicken und Ergebnis auf STDOUT in Empfang
> nehmen.
>
> Schön!!! Aber sehr kryptisch. Könntest Du das Script mal erklären?
Also grundsätzlich ersteinmal: perl -e '...' bedeuted, dass das Perl-
Script direkt mit auf der Kommandozeile mitgeliefert wird. Man hätte es
natürlich auch in eine Datei schreiben können.
Ich habe das Skript erstmal ein wenig entkryptisiert und so umgebaut,
dass es auch einem "use strict" genügt:
my $last = "";
while(my $line = <STDIN>)
{
if ($line =~ /^\//)
{
$line =~ s/^\///;
chomp $last;
}
print $last;
$last = $line;
}
print $last;
Das Script liest die Eingabe Zeile für Zeile ein. Dadurch, dass das
Einlesen direkt als while-Bedingung gemacht wird, terminiert die
Schleife, wenn das Eingabeende erreicht ist. Dabei wird als erstes mit
dem regulären Ausdruck /^\// geprüft, ob die Zeile mit einem / beginnt.
Die umrahmenden / leiten den Regulären Ausdruch ein (und beenden ihn),
das ^ steht für den Zeilenanfang und das \/ ist der /, der mit einem \
escaped werden muss, damit man ihn von den Rand-Slashes unterscheiden
kann. Beginnt die Zeile mit einem /, wird dieser über den
Substitutionsausdruck s/^\/// entfernt (dort ist wieder der reguläre
Ausdruck von oben enthalten; das s sagt, dass ersetzt werden soll und
der letzte Slash trennt das zu Ersetzende ab, hier ein leerer String.
Der eigentliche Aufbau ist folgender: s/das hier ersetzen/durch das
hier/optionen für die Ersetzung).
Außerdem wird mit chomp $last der Zeilenumbruch der letzten Zeile
entfernt. Danach wird mit print $last die letzte Zeile ausgegeben (ggf.
eben ohne Zeilenumbruch am Ende, so das die nächste Zeile dann
angehängt wird). Dann wird die aktuelle Zeile die letzte Zeile. Ganz am
Ende muss dann noch einmal die letzte Zeile ausgegeben werden.
Wir man nun daraus das kryptische von oben macht:
# Deklaration und Initialisierung. Ohne use strict nicht erforderlich
# Der leere String ist Vorgabewert.
my $last = "";
# Anstelle <STDIN> kann man auch <> schreiben als Kurzform
while(my $line = <STDIN>)
{
# Die Substition kann direkt in das if gezogen werden, da auch dort
# wahr zurück gegeben wird, wenn was ersetzt wurde
if ($line =~ /^\//)
{
$line =~ s/^\///;
# Das letzte Semikolon im Block darf fehlen
chomp $last;
}
print $last;
# Hier auch
$last = $line;
}
# Und hier auch
print $last;
Damit hätten wir also folgendes:
while(my $line = <>)
{
if ($line =~ s/^\///)
{
chomp $last
}
print $last;
$last = $line
}
print $last
Jetzt kommt eigentlich der Teil, der es erst richtig kryptisch werden
lässt: Die Verwendung der Variablen $_
Es gibt in Perl eine Reihe von immer definierten Variablen für
verschiedene Zwecke, wobei $_ da mit die wichtigste ist. $_ ist synonym
mit $ARG (was vielleicht schon etwas mehr über die Bedeutung verrät)
und stellt den "standardmäßigen Eingabe- und Pattern-Matching-Raum"
dar. Rein intuitiv enthält diese Variable immer genau das, was Perl
meint, was man als Programmierer als nächstes braucht (naja, zumindest
meistens). Wer es genau wissen will, wann die Variable was enhält, möge
es bitte in entsprechender Perl-Literatur nachlesen; Mit der
Beschreibung kann man fast eine Buchseite füllen. Ich gehe jetzt nur
darauf ein, wo die Variable hier verwendet wird:
# Wir weisen die gelesene Eingabe keiner Variablen zu. Also landet sie
# in $_
while(<>)
{
# Jetzt verwenden wir $_
if ($_ =~ s/^\///)
{
chomp $last
}
print $last;
# Hier auch nochmal
$last = $_
}
print $last
Wenn man nun noch die Variabel $last in $l umbenennt und alle
Zeilenumbrüche entfernt, enthält man das obige.
Im übrigen ist mir beim auseinandernehmen des Scripts aufgefallen, dass
man noch viel mehr optimieren kann:
while(<>)
{
# Nur eine Zeile im if-Block kann man so umstellen
# Außerdem kann man beim Matching die Variable weglassen, dann wird
# implizit $_ verwendet
# Es sind auch andere Trenner beim Pattern-Matching zulässig, damit
# spart man sich das Escapen des /
chomp $last if (s!^/!!);
print $last;
$last = $_
}
print $last
Was dann zu folgendem, wohl noch kryptischerem Ergebnis führt, vor
allem, wenn man wirklich alle unnötigen Leerzeichen noch entfernt:
perl -e'while(<>){chomp$l if(s!^/!!);print$l;$l=$_}print$l'
Im übrigen habe ich noch eine alternative Version gebastelt, die noch
kürzer ist, noch mehr implizite Dinge verwendet und demnach noch
kryptischer ist. Allerdings hat diese Version die Macke, dass am Anfang
ein Zeilenumbruch zu viel ausgegeben wird und am Ende einer fehlt:
perl -pe'chomp;print"\n"unless s!^/!!'
Und warum das ganze so ist und wie das Programm funktioniert, sei dem
geneigten Leser als Übung überlassen. ;)
Gruss
Daniel
Mehr Informationen über die Mailingliste linux