[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