• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

[gelöst] perl: Navigieren in einer Datei

utopos

Member
Hallo zusammen,

ein perl-Skript öffnet eine Datei mit Lesezugriff (entweder explizit, oder das Skript ist ein Einzeiler und wird mit "perl -e '...' DATEI" in der Shell ausgeführt).

Nun kann ich mit der Konstruktion
Code:
while (<>)  {...}

wunderbar die Datei zeilenweise lesen und verarbeiten, auch einzelne Zeilen gezielt überspringen.

Wie aber kann ich, ohne alle Zeilen lesen zu müssen, in der Datei navigieren?
Ich denke dabei etwa an Aktionen der folgenden Typen:

  • Gehe zu Zeile 324 (d.h. Lade diese Zeile nach $_ und ermögliche die Fortsetzung des zeilenweisen Lesens).
  • Gehe - von der aktuell gelesenen Zeile aus - 5 Zeilen nach oben (und erlaube das vorwärts Weiterlesen von dort).
  • Gehe in die 185.-letzte Zeiie.

Alle diese Aufgaben kann man natürlich mit der zeilenweisen Leseschleife nachbauen.
Das ist aber u.U. extrem ineffizient; für die erste Aufgabe etwa müsste ich wieder 324 Zeile lesen, zählen und ignorieren (O(n) Operationen), nur um einen bestimmten Zugriff zu finden (sollte O(1) sein).

Ich habe inzwischen einiges Material zum Dateizugriff in perl gelesen und ausprobiert, aber nichts gefunden. Irgendwie scheinen diese Überlegungen kaum eine Rolle zu spielen.

Könnt Ihr mir dabei weiterhelfen?

Vielen Dank und viele Grüße!
 

abgdf

Guru
Ja, kann ich.

Ohne zusätzliche Hilfen (dazu s.u.) kann man eine Datei nur zum Lesen öffnen (oder zum Lesen und Schreiben), bis zu einer bestimmten Stelle oder bis zum Ende lesen und dann wieder schließen. Will man weiter nach oben in der Datei, muß man den Vorgang wieder von vorne beginnen. Wie Du beschrieben hattest. Das hängt mit dem I/O-System zusammen und ist in allen Sprachen so, auch z.B. in C.

Daneben gibt es "seek()" (perldoc -f seek), dazu mußt Du aber genau wissen, wieviel Bytes Du vor oder zurückspringen willst. Damit kann man normalerweise nicht arbeiten.

Wenn die Datei nicht zu groß ist, kann man sie einmal insgesamt in ein Array einlesen, dann das Array bearbeiten und dieses dann insgesamt einmal schreiben:
Code:
#!/usr/bin/perl

use warnings;
use strict;

# Lesen nach @a:
open(my $fh, "<", "test.txt");
my @a = <$fh>;
close($fh);

print "$a[1]";
print "$a[0]";
Ist die Datei sehr groß, geht das dennoch, und zwar mit dem Modul "Tie::File":
Code:
#!/usr/bin/perl

use warnings;
use strict;

use Tie::File;

# Lesen nach @a:
my @a;
tie(@a, 'Tie::File', "test.txt");

print "$a[1]";
print "$a[0]";

untie(@a);
Tie::File ist schon ziemlich cool.

Übrigens: Wenn Du in Perl-Einzeilern das Verhalten von awk haben willst, die Zeilen automatisch zu teilen, kannst Du Dir hier mal Tipp 5 ff. durchlesen. Im Grunde sollte man sich aber erst mit Perl-Einzeilern beschäftigen, wenn man schon die Grundlagen von Perl kennt. Längere Skripte zu schreiben ist im Prinzip einfacher als diese stark verkürzten und kryptischen Einzeiler.
In Einzeilern kannst Du die Konstruktion
Code:
while (<>)  {...}
z.B. mit den Kommandooptionen "-n" und "-p", also z.B. "perl -ne ..." weglassen, d.h. die Konstruktion wird dann automatisch um den Code in '...' herum ergänzt. Damit es noch kürzer wird.
 
OP
U

utopos

Member
Vielen herzlichen Dank!

Das Laden oder Binden in einen Array löst tatsächlich all die Probleme, die ich geschildert habe.
Habe das "Tie::File" ausprobiert. Meine Dateien sind zwar allenfalls mittelgroß, aber ich möchte die Skripte bzw. das Zum-Lesen-Öffnen einer Datei ziemlich häufig ausführen, so dass das Kopieren aller Daten doch etwas zeitraubend wäre. Ich habe noch keinen Performance-Vergleich angestellt, aber anscheinend ist Tie::File auch hier geeignet (hat mich jedenfalls überzeugt).
Dein Beispiel deckt ja schon eine wichtige Anwendung, die allen gestellten Anforderungen bestens genügt, ab. Anhand Deines Links sehe ich, dass Tie::File aber noch viel umfangreicher ist ... werde mich wahrscheinlich aber vorerst auf die basalen Funktionalitäten beschränken.


abgdf schrieb:
Daneben gibt es "seek()" (perldoc -f seek), dazu mußt Du aber genau wissen, wieviel Bytes Du vor oder zurückspringen willst. Damit kann man normalerweise nicht arbeiten.

Genau! Das hatte ich als Hinweis vor ein paar Monaten gefunden und ausprobiert. Ich erinnere mich nicht mehr an die Details, aber es ging völlig schief ...

abgdf schrieb:
Im Grunde sollte man sich aber erst mit Perl-Einzeilern beschäftigen, wenn man schon die Grundlagen von Perl kennt. Längere Skripte zu schreiben ist im Prinzip einfacher als diese stark verkürzten und kryptischen Einzeiler.

Ich weiß, das ist Dein Ceterum Censeo ...

Unter Einzeilern verstehe ich übrigens nur die Tatsache, dass es mit "perl -e" direkt aufgerufen wird, nicht als Skript in einer Datei abgespeichert ist.
Das muss weder verkürzt noch kryptisch und auch nicht einzeilig sein, denn ich verwende gern Konstruktionen wie:

Code:
perlBefehl='...
             ... mehrzeiliger ....
             ... übersichtlich formatierter ...
             ... perl - Code ...
            ...'
perl -e "$perlBefehl" $objektDatei

Der einzige Unterschied ist, dass ich das Öffnen der Datei syntaktisch auf die Option "-e" verkürzen kann. Was geschieht, ist aber immer noch klar erkenn- und nachvollziehbar. Wie bei echter Sprache macht ein sinnvoll gekürzter Text die Sache oft klarer und nicht unklarer. Aber es ist natürlich Geschmackssache, was sich leichter liest.

Die "-ne" und "-pe" Konstruktionen kenne ich auch. Allerdings habe ich vor oder nach der Schleife noch etwas zu erledigen, so dass ich nicht darauf zurückgreifen kann.


Auf jeden Fall vielen Dank für die hilfreiche und schnelle Antwort!
 
Oben