coolblue: Letzte Zeilen einer sehr großen Datei auslesen

Hallo,

ich möchte gerne die letzten 160 Zeilen einer sehr großen Datei auslesen. Die Datei ist ca. 140 MB groß. Bisher bin ich wie folgt vorgegangen:

-----------------------------------------------

open(DATEI,"<$datei") or die "Can't open $datei!\n";

my $i;
for($i=0 ; <DATEI> ; $i++) {} #Zeilen zählen

close DATEI;

$i-=160; #die besagten 160 Zeilen abziehen
my $n=0;
my @data;

open(DATEI,"<$datei") or die "Can't open $datei!\n";

for(my $s=0 ; <DATEI> ; $s++) {
   if($s >= $i) { #Zeilen ab $i-160 einlesen
      $data[$n]="$_";
      $n++;
   }
}

close DATEI;

-----------------------------------------------

Gibt es eine bessere Möglichkeit, die vorallem effizienter ist? Ich habe zuerst an tail -160 gedacht, möchte aber keine Subshell aufmachen. Gibt es hierfür Perl interne Mittel?

Wäre

for(my $s=0 ; <DATEI> ; $s++) {
   next if $s < $i;
   $data[$n]="$_";
   $n++;
}

das Gleiche, nur eine andere Schreibweise, oder auf jeden Fall schon mal besser als das obere Beispiel?

Viele Grüße,
coolblue

--

never say oops after you submitted a job :-)
_der_Ton_macht_die_Musik_!!!_
  1. 你好 coolblue,


    open(DATEI,"<$datei") or die "Can't open $datei!\n";

    my $i;
    for($i=0 ; <DATEI> ; $i++) {} #Zeilen zählen

    close DATEI;

    $i-=160; #die besagten 160 Zeilen abziehen
    my $n=0;
    my @data;

    open(DATEI,"<$datei") or die "Can't open $datei!\n";

    for(my $s=0 ; <DATEI> ; $s++) {
       if($s >= $i) { #Zeilen ab $i-160 einlesen
          $data[$n]="$_";
          $n++;
       }
    }

    close DATEI;


    Buarghs. Das ist wirklich grausam, was du da tust, du liest die Datei
    voellig unnoetigerweise zweimal aus.

    Gibt es eine bessere Möglichkeit, die vorallem effizienter ist?

    Im Grunde nicht. Du musst in jedem Fall alles von der Datei auslesen.

    Ich habe zuerst an tail -160 gedacht, [...]

    tail macht auch nichts anderes als alles auszulesen und nur den relevanten
    Teil auszugeben ;-)

    Wäre

    for(my $s=0 ; <DATEI> ; $s++) {
       next if $s < $i;
       $data[$n]="$_";
       $n++;
    }

    das Gleiche, nur eine andere Schreibweise, oder auf jeden Fall schon
    mal besser als das obere Beispiel?

    Wenn du auch die Zaehlschleife zum Zaehlen der Zeilen einer Datei
    weglaesst, dann ist das auf jeden Fall sinnvoller.

    再见,
     CK

    --
    Ich bewundere wirklich den Sinn der Bienen für kollektive Verantwortung. Obwohl sich einzelne Bienen hin und wieder bekämpfen, herrscht zwischen Ihnen grundsätzlich ein starkes Gefühl für Eintracht und Zusammenarbeit. Wir Menschen gelten als sehr viel weiter entwickelt, doch mitunter rangieren wir sogar hinter kleinen Insekten.
    http://wwwtech.de/
    1. Hallo Christian,

      Buarghs. Das ist wirklich grausam, was du da tust, du liest die Datei
      voellig unnoetigerweise zweimal aus.

      das verstehe ich nicht! Wie soll ich denn sonst herausfinden, wieviele Zeilen die Datei hat? Die Zeilenanzahl variiert ständig.

      Ich hatte zuallererst an folgendes gedacht:

      @in=<DATEI>;

      aber das wäre wohl total mieß für den Speicher.

      Wenn du auch die Zaehlschleife zum Zaehlen der Zeilen einer Datei
      weglaesst, dann ist das auf jeden Fall sinnvoller.

      wie meinst du das? Wenn ich Zählerschleife weglasse, dann weiß ich doch nicht, wann ich an Zeile $i-160 angekommen bin.

      Oh je, i need help!

      Viele Grüße,
      coolblue

      --

      never say oops after you submitted a job :-)
      _der_Ton_macht_die_Musik_!!!_
      1. 你好 coolblue,

        Buarghs. Das ist wirklich grausam, was du da tust, du liest die Datei
        voellig unnoetigerweise zweimal aus.

        das verstehe ich nicht! Wie soll ich denn sonst herausfinden, wieviele
        Zeilen die Datei hat? Die Zeilenanzahl variiert ständig.

        Das musst du doch gar nicht wissen. Warum interessiert dich das?

        Wenn du auch die Zaehlschleife zum Zaehlen der Zeilen einer Datei
        weglaesst, dann ist das auf jeden Fall sinnvoller.

        wie meinst du das? Wenn ich Zählerschleife weglasse, dann weiß ich doch
        nicht, wann ich an Zeile $i-160 angekommen bin.

        War etwas unueberlegt von mir, zugegeben. Aber egal, speichere die letzten
        160 Zeilen und lies jedesmal, wenn du eine neue Zeile einliest die aelteste
        gespeicherte Zeile weg.

        再见,
         CK

        --
        Der Mund ist das Portal zum Unglück.
        http://wwwtech.de/
        1. Das musst du doch gar nicht wissen. Warum interessiert dich das?

          Weil ich mit den Daten der letzten 160 Zeilen einen Graphen generiere (GD::Graph::lines).

          Viele Grüße,
          coolblue

          --

          never say oops after you submitted a job :-)
          _der_Ton_macht_die_Musik_!!!_
          1. 你好 coolblue,

            Das musst du doch gar nicht wissen. Warum interessiert dich das?

            Weil ich mit den Daten der letzten 160 Zeilen einen Graphen generiere
            (GD::Graph::lines).

            Und warum musst du wissen, wieviele Zeilen die Datei hat?

            再见,
             CK

            --
            lim(3->4)(sqrt(3)) = 2
            http://wwwtech.de/
            1. Und warum musst du wissen, wieviele Zeilen die Datei hat?

              hmmmm...

              Beispiel:

              Eine Datei wird alle paar Sekunden um eine oder mehrere Zeilen erweitert. Nun möchte ich die letzten 160 Zeilen aus dieser Datei lesen, weil die letzten 160 Zeilen die aktuellsten sind. Jetzt hat die Datei zum Beispiel 15768 Zeilen, davon möchte ich die letzten 160 lesen.

              Also gehe ich wie folgt vor: for($i=0 ; <DATEI> ; $i++) {}

              Nun weiß ich, wieviele Zeilen die Datei hat und möchte die letzten 160 davon in einem Array einlesen. Das Array übergebe ich dann meinem Graphen.

              $i-=160; # 15768-=160   ==  15607

              Jetzt lese ich nochmal die Datei:

              for($s=0 ; <DATEI> ; $s++) {
                 next if $s <= $i; # übersetzt: nächste zeile wenn $s kleiner-gleich 15607 ist
                 $data[$n]=$_;     # wenn aber $s größer ist als 15607, lese ein
                 $n++;             # index+1
              }

              Auf diese Weise lese ich die Zeilen 15607 bis 15768 ein, also die letzten 160 Zeilen der Datei. Hätte ich nicht vorher die Datei einmal durchgezählt, wüßte ich nicht, wann die lettzen 160 Zeilen beginnen.

              Viele Grüße,
              coolblue

              --

              never say oops after you submitted a job :-)
              _der_Ton_macht_die_Musik_!!!_
              1. 你好 coolblue,

                $i-=160; # 15768-=160   ==  15607

                Jetzt lese ich nochmal die Datei:

                for($s=0 ; <DATEI> ; $s++) {
                   next if $s <= $i; # übersetzt: nächste zeile wenn $s kleiner-gleich 15607 ist
                   $data[$n]=$_;     # wenn aber $s größer ist als 15607, lese ein
                   $n++;             # index+1
                }

                Auf diese Weise lese ich die Zeilen 15607 bis 15768 ein, also die
                letzten 160 Zeilen der Datei. Hätte ich nicht vorher die Datei
                einmal durchgezählt, wüßte ich nicht, wann die lettzen 160 Zeilen
                beginnen.

                Ja, und ich habe dir doch schon erklaert, wie du das besser machen
                koenntest. Hier mal Beispiel-Code, damit du es besser verstehst:

                open DAT,'<test.txt';

                my $i = 0;
                my $line = '';
                my @lines = ();
                my $wanted_num = 4;

                for($i=0;$line=<DAT>;++$i) {
                  $lines[$i%4] = $line;
                }

                close DAT;

                print foreach(@lines);

                Damit lese ich aus jeder Datei die letzten 4 Zeilen heraus.

                再见,
                 CK

                --
                Es ist uns nicht möglich, in einem Bereich unseres Lebens richtig zu verhalten, wenn wir in allen anderen falsch handeln. Das Leben ist ein unteilbares Ganzes.
                http://wwwtech.de/
                1. Hi,

                  for($i=0;$line=<DAT>;++$i) {
                    $lines[$i%4] = $line;
                  }
                  print foreach(@lines);
                  Damit lese ich aus jeder Datei die letzten 4 Zeilen heraus.

                  gibst sie aber nicht immer in der Reihenfolge aus, in der sie in der Datei stehen.

                  cu,
                  Andreas

                  --
                  Warum nennt sich Andreas hier MudGuard?
                  Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
                  1. 你好 MudGuard,

                    Hi,

                    for($i=0;$line=<DAT>;++$i) {
                      $lines[$i%4] = $line;
                    }
                    print foreach(@lines);
                    Damit lese ich aus jeder Datei die letzten 4 Zeilen heraus.

                    gibst sie aber nicht immer in der Reihenfolge aus, in der sie in der
                    Datei stehen.

                    Nein :) Aber da mir die Anzahl der Zeilen bekannt ist, kann ich die
                    Reihenfolge problemlos feststellen wenn es notwendig ist.

                    再见,
                     CK

                    --
                    Fatal! Ich kann kein Reserve-Offizier mehr sein!
                    http://wwwtech.de/
    2. Hi,

      Im Grunde nicht. Du musst in jedem Fall alles von der Datei auslesen.

      Wirklich?

      Nur mal so als Idee:

      per Seek auf die letzten z.B. 10K positionieren.
      Einlesen bis zum Dateiende.
      Gucken, ob das gereicht hat, um 160 Zeilen zu erwischen.
      Falls ja, die letzten 160 Zeilen benutzen.
      Falls nein, per Seek auf die vorletzten 10K positionieren.
      Nochmal 10K einlesen (etwas aufpassen mit der Nahtstelle zwischen den 2 Blöcken wg. Zeilen).
      Gucken, ob jetzt genug Zeilen eingelesen sind,
      falls ja, diese benutzen,
      falls nein, nochmal 10K holen bis entweder die 160 Zeilen erreicht sind oder der Dateianfang.

      cu,
      Andreas

      --
      Warum nennt sich Andreas hier MudGuard?
      Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
      1. 你好 MudGuard,

        Im Grunde nicht. Du musst in jedem Fall alles von der Datei auslesen.

        Wirklich?

        Nur mal so als Idee:
        [...]

        Das setzt genaue Kenntnis der vorliegenden Daten vorraus (deine Methode
        waere toedlich, wenn z. B. 130MB auf die letzten 160 Zeilen verteilt sind),
        daraus laesst sich kein allgemeingueltig schneller Algorithmus
        formulieren; den Gedanken hatte ich auch, habe ihn aber nicht geaeussert,
        weil ich die Daten des OP nicht kenne.

        再见,
         CK

        --
        Zu wissen, was wir nicht wissen, ist die Quelle der Weisheit.
        http://wwwtech.de/
      2. Hallo Andreas,

        Nur mal so als Idee:

        per Seek auf die letzten z.B. 10K positionieren.
        Einlesen bis zum Dateiende.
        Gucken, ob das gereicht hat, um 160 Zeilen zu erwischen.
        Falls ja, die letzten 160 Zeilen benutzen.
        Falls nein, per Seek auf die vorletzten 10K positionieren.
        Nochmal 10K einlesen (etwas aufpassen mit der Nahtstelle zwischen den 2 Blöcken wg. Zeilen).
        Gucken, ob jetzt genug Zeilen eingelesen sind,
        falls ja, diese benutzen,
        falls nein, nochmal 10K holen bis entweder die 160 Zeilen erreicht sind oder der Dateianfang.

        Das mag hinhauen! Ich lese auch gerne 50k aus, ist noch immer erheblich weniger als 140 MB.

        Hatte von seek bisher nichts gewußt, werde mich aber mal einlesen.

        Wie schauts denn mit meinem Beispiel für kleinere Dateien aus. Wäre das so ok?

        Viele Grüße,
        coolblue

        --

        never say oops after you submitted a job :-)
        _der_Ton_macht_die_Musik_!!!_
  2. Moin,

    hier noch eine Variante mit DB_File, damit wird die Datei an ein array gebunden und liegt in ihrer Gesamtgröße nicht im Speicher 'rum:
    ##################################################################
    use DB_File;
    use strict;

    tie @in, "DB_File", $filename, O_RDWR|O_CREAT, 0644, $DB_RECNO or die $!;

    print scalar @in, " Zeilen hat die Datei, untenstehend die letzten 20:\n";

    for(-20..-1){ print "$in[$_]\n" }

    untie @in;
    ##################################################################

    Gruss, Rolf