Thursday, July 05, 2007

Häufig gebrauchte Variable beim Parsen

Perl ist einfach genial wenn es um die Bearbeitung von Texten und parsen von Dateien geht. Da kommt keine andere Sprache heran. Ein Parser in Java zu schreiben kann sehr umständlich sein - und langsam.

In Perl gibt es viele Möglichkeiten, wie man das Parsen vereinfachen kann. Zum Beispiel wenn es zwischen "Datensätzen" in der Datei einen festen Trenner gibt.

Da kann man einfach $/ auf diesen Trenner setzen und schon erleichtert das einem die Arbeit. Diese Datei soll mal als Beispiel dienen:
Kunde: 123
yadda yadda yadda
yadda yadda yadda
Artikel: rostfreie schrauben

Kunde: 456
yadda yadda yadda
yadda yadda yadda
Artikel: rostfreie schrauben

Kunde: 123
yadda yadda yadda
yadda yadda yadda
Artikel: holzbrett

Man sieht, dass zwischen den Datensätzen immer eine Leerzeile steht. Perl-Einsteiger würden die Datei so parsen:
#!/usr/bin/perl

use strict;
use warnings;

my $file = '/path/to/file.txt';
my %hash;

my ($kunde,$artikel);

open my $fh, '<', $file or die $!;
while( my $line = <$fh> ){
if( $line =~ /^$/ ){
push @{ $hash{$kunde} }, $artikel;
$kunde = '';
$artikel = '';
}
else{
if( $line =~ /Kunde:\s*(\d+)/ ){
$kunde = $1;
}
elsif( $line =~ /Artikel:\s*([^\n]+)/ ){
$artikel = $1;
}
}
}
close $fh;
if( $kunde and $artikel ){
push @{ $hash{$kunde} }, $artikel;
}

for my $key ( keys %hash ){
print $key, " - " , join(" : ", @{ $hash{$key} }),"\n";
}

Aber wenn man $/ auf "\n\n" (zwei Zeilenumbrüche hintereinander - also Leerzeile) setzt, dann erhält man in der while-Schleife immer einen kompletten Datensatz.

Zum Test kann man mal
#!/usr/bin/perl

use strict;
use warnings;

{
local $/ = "\n\n";
my $entry = ;
print $entry;
}


__DATA__
Kunde: 123
yadda yadda yadda
yadda yadda yadda
Artikel: rostfreie schrauben

Kunde: 456
yadda yadda yadda
yadda yadda yadda
Artikel: rostfreie schrauben

Kunde: 123
yadda yadda yadda
yadda yadda yadda
Artikel: holzbrett

ausführen. Dann bekommt man:
C:\>parse2.pl
Kunde: 123
yadda yadda yadda
yadda yadda yadda
Artikel: rostfreie schrauben

So wird das parsen der Datei doch wesentlich einfach und übersichtlicher:
#!/usr/bin/perl

use strict;
use warnings;

my $file = '/path/to/file.txt';
my %hash;

{
local $/ = "\n\n";
my $re = qr/Kunde:\s*(\d+).*Artikel:\s*([^\n]+)/s;
open my $fh, '<', $file or die $!;
while( my $entry = <$fh> ){
my ($kunde,$artikel) = $entry =~ $re;
next unless $kunde;
push @{ $hash{$kunde} }, $artikel;
}
close $fh;
}

for my $key ( keys %hash ){
print $key, " - " , join(" : ", @{ $hash{$key} }),"\n";
}


Man muss sich nicht darum kümmern was passiert wenn am Ende der eingelesenen Datei kein Zeilenumbruch mehr steht und und und...

Was man aber beachten sollte:

Die Variable $/ sollte in einem eigenen Block (hier: das Einlesen) mit local geändert werden. Sonst wird die Variable global geändert. Das kann unerwünschte Nebeneffekte in Modulen oder im eigenen Skript haben (wenn man $/ nicht wieder auf den Defaultwert setzt).

No comments: