letze 10 Einträge in txt
Christian Bliß
- perl
Hallo,
wie kann ich auf die LETZEN 10 Zeilen einer txt-Datei zugreifen?
MfG Christian
Hi!
wie kann ich auf die LETZEN 10 Zeilen einer txt-Datei zugreifen?
Du könntest es mit einem Systemaufruf probieren, AFAIK gibt tail filename
die letzten 10 Zeilen zurück. Wenn es denn mit PERL sein muss, könntest Du immer einen Pointer in einer Variable speichern, der auf den Anfang der zehnt letzten Zeile zeigt, und wenn EOF erreicht ist vom Zeiger bis EOF lesen. Kommt aber stark drauf an wie groß die Datei ist, bei kleineren Dateien kann man es vielleicht auch einrfach mit einem Array machen, also in einen Array lesen und die letzten 10 Elemente irgendwie aussortieren.
Grüße
Andreas
hi,
hab ein wenig gegoogelt und folgendes gefunden:
(ich kenne mich mit perl nicht aus, daher kann es sein,
dass die quelle unbrauchbar für dich ist, but...)
http://www.inspire-world.de/perlcgi/schnipsel/dateihandling.html#M
auszug--------------------------------
13. Letzte Zeile einer Datei auslesen
open (DATEI, "<cat.txt");
@data = <DATEI>;
close (DATEI);
print "@data[-1]";
HIH
mfg
christopher
Halihallo christopher
hab ein wenig gegoogelt und folgendes gefunden:
(ich kenne mich mit perl nicht aus, daher kann es sein,
dass die quelle unbrauchbar für dich ist, but...)
Unbrauchbar ist sie für das Problem zwar nicht, aber sie soll nicht als Vorbild genommen
werden. Die dort vorgestellten Code-Schnipsel sind nicht das wahre.
open (DATEI, "<cat.txt");
@data = <DATEI>;
close (DATEI);
Das zeilenweise Auslesen von Dateien ist zwar zurecht beschimpft, aber für kleine Dateien
für diese Aufgabenstellung mag es OK sein (es gibt bessere Algorithmen).
print "@data[-1]";
print join('',@data[-10..-1]);
gibt die letzten 10 Zeilen aus.
- 1 deshalb weil bei den meisten Dateien am Ende ein Zeilenumbruch steht.
Falsch. -1 deshalb, weil man nur die letzte Zeile will und da diese scalarer Natur ist
schreibt man $data[-1]; in meinem Beispiel von oben wird ein Array aus dem Array gezogen,
das von der -10 Zeile, bis zur -1 reicht (-1 = letztes Array Element = letzte Zeile,
-10 = 10-letztes Array Element = 10-letzte Zeile).
Viele Grüsse
Philipp
Halihallo Forumer
Der Modulo-Operator ([pref:t=52959&m=292762]) hat noch mehr Anwendungsgebiete:
Ich habe mir gerade überlegt, wie man dies a) möglichst performant und b)
speicherschonend umsetzen kann. Hierzu habe ich ein kleines Script geschrieben.
Ich habe zwar noch keine Benchmarks durchgeführt, würde jedoch behaupten, dass es _nicht_
schneller ist (da in Perl; das splicing von Arrays alla @data[-10,-1] geschieht
wesentlich schneller), aber wesentlich weniger Ressourcen verschwendet (je grösser die
Datei desto weniger).
Ich hätte noch einige Performance-Algorithmen, die auch wenig Speicher verschwenden,
wollte es aber mal über einen Modulo-Algo. machen, da dieser mich inspiriert hat :-)
Das Grundprinzip:
Es soll _nicht_ die ganze Datei in ein Array gepackt werden!
Also? -> Zeilenweises auslesen, aber wie kriegt man die letzten 10 Zeilen? - Wir brauchen
eine Art Cache, der die letzten 10 Zeilen speichert. Nur: die letzten Zeilen müssen immer
aus dem Cache rausgeschmissen werden (ein splice _verdoppelt_ die Datenmenge und braucht
auch viel Zeit, was zwar bei kleinen Arrays nicht ins Gewicht fällt, aber was, wenn wir
die letzten 100000 Zeilen wollen?). Tja, hier kommt der Modulo in Einsatz; der Trick
ist, dass die 1 Zeile eben _nicht_ den ersten Array-Index trägt, sondern variabel ist
und einfach die "letzte" Zeile überschreibt (somit brauchen wir den splice nicht mehr).
Tja, dann haben wir aber eine "ungeordnetes" Array, also müssen wir es mit einer for-
Schleife wieder in Ordnung bringen.
Mal sehen, was ihr dazu meint; besonders würde es mich interessieren, wenn jemand eine
bessere Lösung hat (ich weiss nie eine gute, praktikable Antwort auf diese Frage "last
10 lines..."). Hm, im Perl Cookbook steht auch ein Beispiel, soweit ich mich erinnere,
nur habe ich es im Moment nicht vorliegen.
#!/usr/bin/perl
use strict;
my $last_lines_count = 10; # Anzahl Linien
my @last_lines = (); # Array mit letzten linien, temporär!
my $cnt = 0; # aktuelle Zeile
open( F, '<./file.txt' ) || die "cannot open: $!";
while (<F>) {
chomp; # Zeilenumbruch abscheiden, falls vorhanden.
$last_lines[$cnt % ($last_lines_count)] = $_;
# der Kern der Verbesserung: keine Verschiebung oder splice von Arrays nötig.
$cnt++; # nächste Zeile...
}
close F || die "cannot close: $!";
for ( my $i=$cnt; $i < $cnt + $last_lines_count; $i++ ) {
print $last_lines[$i % ($last_lines_count)] . "\n";
}
Viele Grüsse
Philipp
Hi Philipp Hasenfratz,
Der Modulo-Operator ([pref:t=52959&m=292762]) hat noch mehr Anwendungsgebiete:
Ich habe mir gerade überlegt, wie man dies a) möglichst performant und b)
speicherschonend umsetzen kann.
hast Du mal den Quelltext von "tail" gelesen?
Mein Ansatzpunkt für eine wirklich performante [tm] Lösung wäre, alles zu verwenden, was an Information vorhanden ist.
Im Verzeichniseintrag der Datei innerhalb des Dateisystems muß es irgendwie eine Beschreibung geben, welche Blöcke auf der Festplatte die entsprechende Datei enthalten. Das mag dateisystemspezifisch sein - aber das ist mir im Moment egal (vielleicht gibt es eine API dafür in einer System-Bibliothek).
Ein Programm könnte also
1. den letzten Block der Datei einlesen,
2. diesen in Sätze zerlegen,
3. zählen, ob das genug Sätze sind, und
a) ja => die passenden Sätze extrahieren und aufhören bzw.
b) nein => den vorletzten Block lesen und weiter bei 2.
Dies würde die Disk-I/O-Rate bei großen Dateien wahrscheinlich stark reduzieren.
Das Grundprinzip:
Es soll _nicht_ die ganze Datei in ein Array gepackt werden!
Eben. Meine Idee versucht sogar, gar nicht erst die ganze Datei zu lesen.
Viele Grüße
Michael
Hallo Michael,
Ein Programm könnte also
- den letzten Block der Datei einlesen,
- diesen in Sätze zerlegen,
- zählen, ob das genug Sätze sind, und
a) ja => die passenden Sätze extrahieren und aufhören bzw.
b) nein => den vorletzten Block lesen und weiter bei 2.
Diese Idee hatte ich gestern abend auch, allerdings bin ich mit ihrer Umsetung an meinen nicht wirklich guten Perl-Kenntnissen gescheitert.
Heute habe ich mir überlegt, dass, bevor ich groß rummache, ich das ganze lieber in PHP schreibe, das kann ich wenigstens: http://www.christian-seiler.de/temp/lastlines.phps
Ich habe Philipps Beispiel mal in PHP übersetzt (um gleiche Testbedingungen zu machen) und die Werte für eine Textdatei mit 12000 Zeilen und einem Knappen MB getestet.
Folgende Werte für 1000 Durchläufe: (die Datei dürfte im Speichercache liegen, d.h. bei noch auf der Festplatte liegenden Dateien dürfte der Geschwindigkeitsunterschied noch größer sein.
Dabei hat Philipps Beispiel durchschnittlich so abgeschnitten:
real 3m16.526s
user 2m35.260s
sys 0m7.020s
Mein Beispiel dagegen erledigte die Aufgabe in durchschnittlich:
real 0m32.489s
user 0m26.660s
sys 0m4.380s
Viele Grüße,
Christian
Christian Seiler
Hallo,
durchschnittlich
durchschnittlich
*gnargh*, das soll natürlich nicht durschschnittlich sondern insgesamt (alle 1000 Durchläufe) heißen, sonst wäre beides wirklich sehr langsam. :)
Viele Grüße,
Christian
Hallo Christian,
Mein Beispiel dagegen erledigte die Aufgabe in durchschnittlich:
real 0m32.489s
user 0m26.660s
sys 0m4.380s
schön. Hast Du mal einen Benchmark-Vergleich gegenüber dem normalen "tail" gemacht? ;-)
Wie plattformspezifisch ist Deine Schnittstelle zum Lesen der Blockliste einer Datei?
(Das ist ja wohl der Knackpunkt - der Rest dürfte nicht mehr schwierig zu implementieren sein ... abgesehen davon, daß man die gelesenen Sätze irgendwie verketten muß, wenn der letzte Block nicht ausreicht.)
Viele Grüße
Michael
Hallo Michael,
Hast Du mal einen Benchmark-Vergleich gegenüber dem normalen "tail" gemacht? ;-)
Ähem:
real 0m4.306s
user 0m2.300s
sys 0m1.470s
;-)
Wobei ich hier noch sagen muss, dass ich jedes Mal den PHP-Interpreter neu geladen habe, d.h. wenn ich das Script nur per include() einbinden würde, dann wären _beide_ Scripte sicherlich auch noch etwas schneller, ob in dieser Größenordnung vermag ich nicht zu sagen. (vielleicht probiere ichs noch, mal sehen)
Wie plattformspezifisch ist Deine Schnittstelle zum Lesen der Blockliste einer Datei?
Gar nicht plattformspezifisch. Die PHP-Funktion stat() ist überall dort verfügbar, wo PHP läuft. Sie liefert einen Array zurück. Der Wert mit dem Index 12 enthält die Blockgröße des Dateisystems; falls diese nicht verfügbar ist, steht dort -1. Ich nehme in diesem Fall eine Blockgröße von 1 KB an. Sonst verwende ich nur Funktionen aus ANSI C, die auch in PHP zur Verfügung stehen; d.h. wenn ich das Programm nach C portieren würde, dürfte es auf allen Plattformen mit ANSI-C-Kompiler laufen, sofern ich mir etwas für den stat()-Aufruf ausdenke.
(Das ist ja wohl der Knackpunkt - der Rest dürfte nicht mehr schwierig zu implementieren sein ... abgesehen davon, daß man die gelesenen Sätze irgendwie verketten muß, wenn der letzte Block nicht ausreicht.)
In meinem PHP-Script ist das bereits implementiert.
Viele Grüße,
Christian
Halihallo Christian
Ich habe Philipps Beispiel mal in PHP übersetzt (um gleiche Testbedingungen zu machen) und die Werte für eine Textdatei mit 12000 Zeilen und einem Knappen MB getestet.
Hm, und was ist mit last-100-Zeilen von einem Text mit 130 Zeilen? - Ist doch auch ein
realistischer Use-Case? - Wird aber wohl auch etwas schneller sein, schätze ich.
Dabei hat Philipps Beispiel durchschnittlich so abgeschnitten:
real 3m16.526s
Mein Beispiel dagegen erledigte die Aufgabe in durchschnittlich:
real 0m32.489s
Ohhh, schande über mich :-)
---
Etwas verspätet, aber dennoch kommend: die Perl-Version (danke für die PHP Version!)
---
#!/usr/bin/perl
use strict;
my $lines = 10; # how many lines do ya want?
my $chunk_size = 100; # each line has an avarage of 100 bytes.
open( F, '<./input.txt' ) || die "cannot open input: $!";
binmode(F) || die "cannot binmode the handle: $!";
my $buf = ''; # define them here instead of in the while is much better!
my $tbuf= '';
my $offset = -1*($chunk_size+1); # last character in file is -1, so first
# chunk begins at -(chunk+1)
my $lines_read = 0; # how many lines have we already found?
while ( $lines_read <= $lines ) { # as long as we haven't read enough...
seek(F,$offset,2); # seek next chunk
unless (read(F,$tbuf,$chunk_size)) {
last; # perhaps error (beginning of file passed),
# or eof which is not very common here :-)
}
$lines_read += ($tbuf =~ tr/\n//); # unfortunately \015\012|\015|\012 is not
# possible here :-(, but everything else
# costs more...
$buf = $tbuf.$buf;
$offset-=$chunk_size; # we select chunk before if we need more lines.
}
close(F) || die "cannot close input: $!";
my @last_lines = (split(/\n/,$buf))[(-1*$lines)..-1];
print join("\n", @last_lines);
---
Könnte man zwar auch noch etwas feilen, aber dazu habe ich im Moment weder Lust noch
Zeit; aber für Gedanken, Fehler, Verbesserungsvorschlägen und anderem habe ich "immer"
Lust und Zeit.
Viele Grüsse
Philipp
Hallo Philipp,
Hm, und was ist mit last-100-Zeilen von einem Text mit 130 Zeilen? - Ist doch auch ein
realistischer Use-Case? - Wird aber wohl auch etwas schneller sein, schätze ich.
jetzt brauchen wir langsam eine exakte Aufgabenstellung. Was genau willst Du erreichen?
Willst Du die Maschinenlast über die reale Verteilung existierender Dateigrößen minimieren, oder willst Du einen Algorithmus implementieren, der eine benutzerfreundliche Antwortzeit garantiert?
Im ersten Falle mußt Du die häufigen Zugriffe schnell machen, im zweiten die langsamen ...
Viele Grüße
Michael
Halihallo Michael
jetzt brauchen wir langsam eine exakte Aufgabenstellung. Was genau willst Du erreichen?
Ich wünschte ich könnte Dir diese Frage beantworten, aber ich kenne keine Antwort. Mir
ging es, wie ich im Ausgangsposting sagte, um eine allgemeine Antwort auf die "last-lines"
Frage. Mir ging es desweiteren darum, eine Lösung nicht über das komplette einlesen der
Datei zu erreichen (ressourcensparend). Ein weiterer Aspekt war natürlich auch die
Verarbeitungsgeschwindigkeit.
Ich habe keine konkrete Aufgabenstellung, ich habe sie allgemein gehalten. Deshalb gibt
es auch duzende von Lösungen, die allesammt der Aufgabenstellung genügen (die Lösung
über Modulus war eine, die Lösung über binäre Dateien und "Cache" eine andere). Ich habe
lediglich einen guten Kompromiss gesucht, der besser als das gesamte Einlesen der Datei
ist und dazu noch schneller sein soll... Das war das Diskussionsthema, Diskussion
deswegen, weil es eben viele Meinungen und viele Algorithmen gibt.
Willst Du die Maschinenlast über die reale Verteilung existierender Dateigrößen minimieren, oder willst Du einen Algorithmus implementieren, der eine benutzerfreundliche Antwortzeit garantiert?
Im ersten Falle mußt Du die häufigen Zugriffe schnell machen, im zweiten die langsamen ...
... ob dieser nun für möglichst alle realen Verteilungen optimiert ist, oder nur der
möglichst kleinen Antwortzeit dient, ist eigentlich gar nicht in der Aufgabenstellung
enhalten gewesen. Aber im Sinne einer allgemeinen Lösung, würde ich das Kriterium:
Möglichst performante und ressourcenschonende Verarbeitung möglichst aller realen
Verteilungen von Dateigrössen.
in die Aufgabenstellung einbeziehen. Auch wieder "wischi-waschi", unklar definiert. Mir
geht es eben nicht um einen konkreten Anwendungsfall, sondern um eine allgemeine Lösung,
wie sie z.B. tail implementiert, nur, dass ich dies gerne in Perl formuliert hätte.
Mir geht es um die Formulierung und Umsetzung eines Algorithmus, den man auf die
"last-lines" Frage posten kann und den meisten Anwendungsfällen genügt (er muss also auch
nicht bestimmte Fälle optimieren). Die Lösung über Modulus genügt dem eigentlich auch
schon, denn sie ist schneller und lädt nicht die ganze Datei in den Speicher. Die Lösung
über binäre Behandlung ist auch gut und optimiert für grösse Dateien, wobei die kleinen
ebenso performant verarbeitet werden.
Viele Grüsse
Philipp
Hi,
Ein Programm könnte also
- den letzten Block der Datei einlesen,
- diesen in Sätze zerlegen,
- zählen, ob das genug Sätze sind, und
a) ja => die passenden Sätze extrahieren und aufhören bzw.
b) nein => den vorletzten Block lesen und weiter bei 2.
Vielleicht kann man dafür auch die seek()-Funktion verwenden:
seek(FILEHANDLE, $offset, SEEK_END);
Wobei man $offset mit 0 beginnt und immer um 1 verringert. Ob das allerdings eine gute Lösung ist, weiß ich nicht.
Viele Grüße
Torsten
Halihallo Michael
hast Du mal den Quelltext von "tail" gelesen?
Nein, aber deine Ausführungen reichen für das Verständnis.
Mein Ansatzpunkt für eine wirklich performante [tm] Lösung wäre, alles zu verwenden, was an Information vorhanden ist.
Hm. Angestrebt war eine Lösung in Perl, aber das Verfahren liesse sich portieren (s.
Christian Seilers Beispiel, danke Christian!).
Dies würde die Disk-I/O-Rate bei großen Dateien wahrscheinlich stark reduzieren.
Stimmt. Ich mag mich da an einen log-file-analyser erinnern, der auch auf diese Weise
performant Daten selektieren sollte (die Suche ist grad überlastet, sonst würde ich
verlinken).
Eben. Meine Idee versucht sogar, gar nicht erst die ganze Datei zu lesen.
Ja, der Vorschlag ist sehr gut! - Die binäre/random-access Lösung finde ich sehr gut und
ist für eine wirklich effiziente Lösung nötig. Ich war hier zu textuell an das Problem
gegangen :-) [naja, ich wollte nicht die perfekteste aller Lösungen, sondern eine, die
auch halbwegs in 300 Zeichen unterzubringen ist und man für die 10-last-lines Frage
verwenden kann]
Danke an alle für den Input!
Viele Grüsse
Philipp
Hi Philipp,
gegangen :-) [naja, ich wollte nicht die perfekteste aller Lösungen, sondern eine, die
auch halbwegs in 300 Zeichen unterzubringen ist und man für die 10-last-lines Frage
verwenden kann]
die war doch schon mit tail -10 $filename
abgehakt ... ;-)
(<veg>Ich lese in Deinem Satz nichts von "plattformunabhängig"</veg>)
Viele Grüße
Michael
Halihallo Michael
die war doch schon mit
tail -10 $filename
abgehakt ... ;-)
(<veg>Ich lese in Deinem Satz nichts von "plattformunabhängig"</veg>)
Korrekt, so ist dieses Kriterium hiermit nachträglich hinzugefügt :-)
---
Die Lösung über binäre Behandlung ist eigentlich auch in 300 Zeichen (+-) unterzubringen,
nur stört mich dieser Medienbruch (Konversion von text zu binär), aber was tut man nicht
alles für die Performance und nun erwarte ich deinen erneuten Einwurf:
"dann verwende endlich tail -10 $filename
."
:-)
Viele Grüsse
Philipp