Seite 1 von 1

Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 10:19
von AndiArbeit
Hallo liebes Python Forum,

ich möchte aus einer sehr großen Textdatei (<50mb) über Netzwerk die letzten 5 Zeilen der Datei auslesen.

Hierfür ist der readlines() Befehl suboptimal.

Code: Alles auswählen

      client_msg = paramiko.Transport((rechner, 22))
      client_msg.connect(username=login.name, password=login.passwort)
    
      sftp = paramiko.SFTPClient.from_transport(client_msg)
    
      my_file = sftp.open(datei, 'r')
      text = my_file.readlines()
      
      tkMessageBox.showinfo(self.name, text[-5:])
      
      my_file.close()
    
      client_msg.close()
Gibt es eine Möglichkeit von vornherein auschließlich das Ende einer Datei auszulesen?

Gruß,
AndiArbeit

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 10:34
von __blackjack__
Falls `my_file` die `seek()`-Methode relativ zum Ende der Datei unterstützt, könntest Du versuchen abzuschätzen wie viel man mindestens lesen muss damit da die letzten 5 Zeilen bei sind. Und Du müsstest natürlich auch den Fall berücksichtigen wenn Du Dich verschätzt hast.

Edit: Und natürlich auch das es sich vielleicht um ein Encoding mit mehr als einem Byte pro Zeichen handelt, und man beim schätzen der Leseposition die auch mitten in einem Zeichen landen kann das durch mehr als ein Byte kodiert ist.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 12:05
von Sirius3
@__blackjack__: zum Glück ist beim am häufigsten eingesetzen Multibyte-Encoding (utf8) klar, ob es sich um ein Anfangsbyte oder ein Folgebyte handelt.

Alternativ könnte man mit ssh auch ein tail-Kommando absetzen.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 13:13
von DeaD_EyE
Man könnte ungefähr schätzen wie viel Bytes insgesamt die n gewünschten Zeilen zusammen haben.

Code: Alles auswählen

def tail_file(file, n):
    fsize = os.path.getsize(file)
    buffersize = min(fsize, 4096)
    with open(file, 'rb') as fd:
        buffer = bytearray(buffersize)
        fd.seek(-buffersize, 2)
        amount = 0
        while amount < buffersize:
            chunk_size = fd.readinto(buffer)
            if not chunk_size:
                break
            amount += chunk_size
    lines = [chunk.decode() for chunk in buffer.splitlines()]
    return lines[len(lines) - n:]
Der Kernpunkt ist eigentlich nur fd.seek(x, 2). Das zweite Argument gibt an, dass eine relative Position vom Ende aus verwendet wird.
Negative Werte in der relativen Positionierung sind nur möglich, sofern die Datei im binären Modus geöffnet worden ist.
Nutzt man den Text-Modus, funktioniert die negative Rückwärtssuche vom Ende der Datei nicht.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 13:47
von lackschuh
OT:
Mittels

Code: Alles auswählen

tail -1 wordlist.txt
spuckt mein lahmer RPi1 ratzfatz die letzte Zeile einer Datei, mit 1.5 Mio. Zeilen aus.
https://de.wikipedia.org/wiki/Tail_(Unix).
Ob eine Python-Lösung schneller/langsamer/gleich ist, kann ich aber nicht sagen.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 13:58
von narpfel
@lackschuh: Die einfachste Implementierung von `tail` in Python dürfte in etwa so aussehen:

Code: Alles auswählen

from collections import deque

def tail(path, line_count):
    with open(path) as lines:
        return list(deque(lines, line_count))
Das dürfte aber viel langsamer als `tail` sein.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 14:03
von lackschuh
@narpfel
die DeaD_EyE Funktion ist um ein vielfaches schneller als deine ;)

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 14:26
von narpfel
@lackschuh: Die liest ja auch nicht die ganze Datei ein.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 14:31
von Sirius3
@DeaD_EyE: es ist davon auszugehen, das `read` immer bis zum Ende der Datei liest. Dein Code berücksichtigt weder, dass die letzten Zeilen länger als 4096 Bytes sein könnten, noch dass Du UTF8-Bytesequenzen zerschneidest. Außerdem dürfte os.path.getsize mit paramiko nicht funktionieren.
Mit den selben Fehlern, aber kürzer:

Code: Alles auswählen

def tail_file(filename, n):
    fsize = os.path.getsize(filename)
    buffersize = min(fsize, 4096)
    with open(filename, 'rb') as fd:
        fd.seek(-buffersize, 2)
        buffer = fd.read()
    lines = buffer.decode().splitlines()
    return lines[-n:]

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 15:44
von DeaD_EyE
Meine ursprüngliche Idee war es, die Datei in Chunks solange rückwärts einzulesen, bis 1. die gewünschte Anzahl der Zeilen erreicht ist und 2. keine Zeilen zerrissen werden.
Die Ausgabe sollte natürlich in richtiger Reihenfolge erfolgen.

tail_file muss lokal auf der Kiste ausgeführt werden.
Das du jetzt mein bytearray einfach so rausgenommen hast, macht mich jetzt ganz traurig :-(
Letztendlich bring es bei der geringen Datenmenge auch keinen Geschwindigkeitsvorteil.


Übrigens kann man Bytes gefahrenlos bei '\n' splitten, auch wenn diese mit UTF-8 kodiert sind.
Wenn du dir den Algorithmus ansiehst, wirst du es verstehen: https://de.wikipedia.org/wiki/UTF-8#Algorithmus
UTF-8 ist mit ASCII deckungsgleich. Sobald ein Zeichen aus mehreren bytes besteht, ist das MSB auf 1 gesetzt.
Da die Quelle der Datei uns unbekannt ist, kann man da nur raten.
Ich rate mal, dass die Ausgabe UTF-8 ist und lediglich '\n' als Newline verwendet wird.

Eine verbesserte Funktion würde natürlich alle Gegebenheiten berücksichtigen und Zeilentrennung erst nach einem decode durchführen.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Dienstag 5. Juni 2018, 19:54
von snafu
@AndiArbeit:
Leider gibt es vorab keine Information, wieviele Zeilen eine Datei hat oder wo die Zeilenumbrüche stecken. Das Betriebssystem liefert sowas nicht. Also muss dein Programm sich zwangsläufig jede Zeile ansehen und überschüssige Zeilen wegwerfen, was bei großen Dateien relativ zeitaufwändig sein kann. Oder man geht - wie gezeigt - das Risiko, dass man zu einer Stelle springt, die vermutlich noch vor den letzten gewünschten Zeilen kommt und spart dadurch Zeit, weil man mitunter jede Menge Zeilen überspringen kann. Mit einer festen Positionsangabe würde ich dann aber nicht arbeiten. Eher mit einer maximalen Zeilenlänge (z.B. maximal 120 Zeichen pro Zeile) und die dann mit der Zeilenzahl multiplizieren, um daraus die Position zu berechnen. Das hängt aber stark von der vorliegenden Datei ab. Generiertes HTML zum Beispiel kann manchmal ziemlich lange Zeilen haben und 120 könnte dann viel zu wenig sein.

Re: Letzten Zeilen aus Datei lesen.

Verfasst: Mittwoch 6. Juni 2018, 08:36
von DeaD_EyE
Ganz andere Idee: Lass doch ein Programm mitlaufen (auf dem Server selbst), dass die entsprechenden Zeilen filtert und getrennt zusätzlich in eine andere Datei ausgibt.
Das Format kann ja komplett identisch sein.

Ein Möglichkeit wäre es mit inotify die Logdatei zu überwachen.
Sobald eine neue Zeile geloggt wird, wird dann dein Programm aktiv und macht nur etwas, wenn die neue Zeile das beinhaltet, wonach du suchst.

Könnte dann so aussehen:

Code: Alles auswählen

import inotify.adapters


def filter_log(logfile, filter_func, encoding='utf-8'):
    last_size = os.path.getsize(logfile)
    notify = inotify.adapters.Inotify()
    notify.add_watch(logfile)
    for ev, ev_type, path, filename in notify.event_gen(yield_nones=False):
        if ev_type == ['IN_MODIFY']:
            with open(logfile, 'rb') as log:
                log.seek(last_size)
                new_data = log.read().decode(encoding)
            last_size = os.path.getsize(logfile)
            if filter_func(new_data):
                 yield new_data
Den Code kannst du ja zuvor mit der Datei /tmp/test ausprobieren.

Code: Alles auswählen

for data in filter_log('/tmp/test', lambda x: True):
    print(data, end='')
Dann in der Shell:

Code: Alles auswählen

echo -e "123" >> /tmp/test
echo -e "456" >> /tmp/test
echo -e "789" >> /tmp/test
Anstatt "lambda x: True" übergibst du deine eigene Filterfunktion.
Vielleicht lässt sich aber diese ganze Aufgabe auch deinem Loggingsystem zuweisen.
Über Regeln kann man da auch bestimmt einiges erreichen.