(Perl/Datenstruktur) Hash of Files?
Michael Schröpl
- programmiertechnik
Hallo Leute,
das Problem, das ich vor mir liegen habe, ist vermutlich so knifflig, daß ich nur von einer Handvoll Leser eine Reaktion erwarte.
Es geht mir darum, in Perl eine Datenstruktur zu entwerfen, mit der ich eine größere Menge von Eingabezeilen geschickt auf dynamisch viele Dateien verteilen kann - welche Dateien das sind, steht in gewisser Weise in den Zeilen selbst drin.
Die Zeilen enthalten Felder (Feldnummern und Inhalt) und lassen sich mit split() elegant auftrennen.
Einige der Felder besitzen ein implizites Datum, andere besitzen ein explizites Datum in Form des Inhalts eines bestimmten anderen Feldes.
Meine Aufgabe besteht darin, jeweils die Felder der aktuellen Zeile in die dem Datum zugehörige Datei zu schreiben.
Am Ende sollen also viele Dateien desselben Aufbaus wie die Eingabedatei stehen, aber mit jeweils nur dem für sie relevanten Teil der Informationen. (Die werden dann von einem weiteren Prozeß einzeln weiterverarbeitet.)
Im Prinzip habe ich fast alles verfügbar (die Tabellen, welches Feld von welchem anderen abhängt oder auch nicht, lese ich aus Konfigurationsdateien ein).
Bloß weiß ich nicht recht, wie ich die dynamisch vielen Ausgabedateien parallel ansprechen soll.
Für jedes Feld eines jeden Datensatzes die passende Datei zu öffnen und zu schließen, das wird zu langsam (die Eingabe sind 10 MB, die mittlere Feldlänge vielleicht 20 Bytes, und das ist nur *einer* von vielen Verarbeitungsschritten).
Die Eingabe pro Datum mehrfach zu verarbeiten geht nicht so gut, weil in den Datumsfeldern theoretisch *irgendwas* drin stehen kann.
Ich könnte die Daten in irgendwelchen Arrays zwischenspeichern (ein Array pro Datei, diesen müßte ich über das Datum indirekt adressieren - ich bräuchte dann ein hash of array oder müßte in einem hash die Zeilennummer eines arrays of array ablegen ...) statt sie sofort auszugeben, aber dann habe ich die ganzen Daten im Speicher, und das will ich ungern tun (es können mehr als diese 10 MB werden, weil bestimmte Teile der Informationen in *alle* Dateien müssen), und das Ganze muß auf einem WindowsNT4-PC laufen, von dem ich noch nicht weiß, wie gut ausgebaut er sein wird und was auf ihm noch alles gleichzeitig laufen wird.
Was ich also ungefähr suche, das ist entweder ein hash of filehandle (dann könnte ich alle Ausgabedateien bei Bedarf dynamisch öffnen, parallel offen halten und über das Datum adressieren) oder einen hash of pointer to file oder so was Ähnliches.
Leider weiß ich in Perl nicht arg viel über Pointer und Filehandles - das Offensichtliche habe ich schon mal gemacht, aber für meinen Fall reicht es nicht aus.
Mir würde schon sehr helfen, wenn mir jemand für eines der angedeuteten Modelle sagen würde "das geht, so würde ich es machen uns es schreibt sich etwa soundso".
hi!
Was ich also ungefähr suche, das ist entweder ein hash of filehandle (dann könnte ich alle
Ausgabedateien bei Bedarf dynamisch öffnen, parallel offen halten und über das Datum
adressieren) oder einen hash of pointer to file oder so was Ähnliches.
Mit Typeglobs kannst du von einem bereits existierenden Filehandle eine Variable erzeugen:
$filehandle = *FILEHANDLE;
print $filehandle "Hallo Filehandle!\n";
Typeglobs dienten ursprünglich dazu, Arrays und Hashes über eine Referenz an Funktionen zu übergeben (bevor es echte Referenzen gab).
Mit einer kleinen Funktion kannst du dir aus lokalen Filehandles Variablen erzeugen:
sub return_fh
{
my $filename = shift;
local *FH; # local, nicht my!
open FH, "<$filename" or return undef;
return *FH;
}
$handle = &return_fh("datei.txt");
Die so erzeugten Skalare solltest du dann auch problemlos in Hashes oder Arrays speichern können.
Eine weitere Möglichkeit, über die ich nichts genaueres schreiben möchte, sind die Module IO::File und IO::Handle, mit denen du Filehandles als Objekte anlegen und mit ihnen arbeiten kannst.
Mein Perl-Kochbuch nennt als Referenzen mit weiteren Informationen die open-Funktion in perlfunc(1) und in Kapitel 3 von "Programming Perl", die Dokumentation des FileHandle-Moduls (auch in Kapitel 7 von "Programming Perl") und "Typeglobs and Filehandles" aus Kapitel 2 von "Programming Perl".
bye, Frank!
Mit Typeglobs kannst du von einem bereits existierenden Filehandle eine Variable erzeugen:
$filehandle = *FILEHANDLE;
print $filehandle "Hallo Filehandle!\n";
Typeglobs dienten ursprünglich dazu, Arrays und Hashes über eine Referenz an Funktionen zu übergeben (bevor es echte Referenzen gab).
Super, da wäre ich so schnell niemals drauf gekommen! Genau so etwas suchte ich ... :-)))
Mit einer kleinen Funktion kannst du dir aus lokalen Filehandles Variablen erzeugen:
sub return_fh
{
my $filename = shift;
local *FH; # local, nicht my!
Hoppla, daran wäre ich eventuell noch gescheitert.
Was genau macht denn "local" hier?
(Insbesondere: Überlebt es den Funktionsaufruf?)
Ich brauche die Files nämlich eine ganze Weile, der hash wird eine globale Variable sein müssen ... und ich brauche viele davon ...
Die so erzeugten Skalare solltest du dann auch problemlos in Hashes oder Arrays speichern können.
Fein!
Ich werde mir also eine Funktion
store ($record, $datum)
bauen, die
1. nachsieht, ob die zu $datum passende handle im hash schon existiert (und bei Bedarf mit Deiner Funktion eine neue öffnet und das handle im Hash ablegt) und
2. $record in diese Datei schreibt.
(Falls das Öffnen nicht ging, dann haben wir halt ein Problem ...)
Das löst mein Problem perfekt!
(Die ganze Konfigurationsdatei-Einleserei - das Ganze muß nämlich DAU-konfigurierbar sein - habe ich in der Zwischenzeit schon mal geschrieben ...)
Eine weitere Möglichkeit, über die ich nichts genaueres schreiben möchte, sind die Module IO::File und IO::Handle, mit denen du Filehandles als Objekte anlegen und mit ihnen arbeiten kannst.
Aber dies durchzuarbeiten, wollte ich mir ohne jegliche Ahnung von Objekten nicht antun, zumal das Programm besser heute noch halbwegs fertig werden sollte ...
Mein Perl-Kochbuch nennt als Referenzen mit weiteren Informationen die open-Funktion in perlfunc(1) und in Kapitel 3 von "Programming Perl", die Dokumentation des FileHandle-Moduls (auch in Kapitel 7 von "Programming Perl") und "Typeglobs and Filehandles" aus Kapitel 2 von "Programming Perl".
Danke, werde ich mir alles merken - wenn ich mal wieder Zeit dafür habe, das wird in nächster Zeit wahrscheinlich etwas enger werden ...
Hallo Michael,
Mit einer kleinen Funktion kannst du dir aus lokalen Filehandles Variablen erzeugen:
sub return_fh
{
my $filename = shift;
local *FH; # local, nicht my!
(Insbesondere: Überlebt es den Funktionsaufruf?)
Also ueberleben tut local die Funktion nicht. Dafuer sind local und my ja da, damit die Variablen u.a. nach so einer Funktion gleich wieder ins Gras beissen ;-)
arum allerdings hier jetzt local und nicht my ist mir auch nicht so ganz klar. Ich weiss nur dass my schneller ist als local und dass es erst ab Perl 5 verfuegbar ist.
Viele Gruesse
Beate Mielke
Mit einer kleinen Funktion kannst du dir aus lokalen Filehandles Variablen erzeugen:
sub return_fh
{
my $filename = shift;
local *FH; # local, nicht my!
open FH, "<$filename" or return undef;
return *FH;
}
$handle = &return_fh("datei.txt");Die so erzeugten Skalare solltest du dann auch problemlos in Hashes oder Arrays speichern können.
Yup, es läuft! Vielen Dank, Frank!!!
(Mit den aktuellen Testdaten von heute werden übrigens *355* Dateien parallel erzeugt - gut, daß ich nicht ernsthaft darüber nachgedacht habe, die Datei einmal pro Datum zu verarbeiten ... mit der vorliegenden Lösung geht die zusätzliche Ausgabe gegenüber dem Einlesen der Datei und dem Zerlegen per regexps nahezu in Nullzeit ...)
Mein Modul dazu: (cut&paste ...)
package zerleger_store;
$zerleger_store::outdir_name = "";
my %filehandles = ();
sub new_file ($)
{
# Parameter zuweisen
my ($filename) = @_;
# Ein lokales typeglob eines filehandles (?)
local *HANDLE;
# Eine neue Datei zum Schreiben öffnen
if (open (HANDLE, ">$filename"))
{
# Filehandle als Ergebnis zurückliefern
return *HANDLE;
}
else
{
# Fehlermeldung und Abbruch
abort ("new_file / $filename: open[write] gescheitert");
}
}
sub value ($$)
{
# Parameter zuweisen
my ($datum, $wert) = @_;
# Ist die passende Ausgabedatei bereits geöffnet?
if (! defined ($filehandles {$datum}))
{
# Nein, wir müssen also eine neue öffnen ...
$filehandles {$datum} = new_file ("$zerleger_store::outdir_name\$datum.dat");
}
# *Jetzt* haben wir die Datei,
# oder das Programm ist in new_file abgebrochen worden ...
# Nun können wir den Wert in die Datei ausgeben
my $file = $filehandles {$datum};
print $file $wert;
}
1;