Die effizienteste Methode, um die letzten X Zeilen einer Datei zu durchsuchen?

Lesezeit: 8 Minuten

Die effizienteste Methode um die letzten X Zeilen einer Datei
Harley Holcombe

Ich habe eine Datei und weiß nicht, wie groß sie sein wird (sie könnte ziemlich groß sein, aber die Größe variiert stark). Ich möchte die letzten 10 Zeilen oder so durchsuchen, um zu sehen, ob eine davon mit einer Zeichenfolge übereinstimmt. Ich muss dies so schnell und effizient wie möglich tun und habe mich gefragt, ob es etwas Besseres gibt als:

s = "foo"
last_bit = fileObj.readlines()[-10:]
for line in last_bit:
    if line == s:
        print "FOUND"

  • Exaktes Duplikat von stackoverflow.com/questions/136168/tail-a-file-with-python.

    – S.Lott

    4. November ’08 um 2:05

1642002846 57 Die effizienteste Methode um die letzten X Zeilen einer Datei
PabloG

# Tail
from __future__ import with_statement

find_str = "FIREFOX"                    # String to find
fname = "g:/autoIt/ActiveWin.log_2"     # File to check

with open(fname, "r") as f:
    f.seek (0, 2)           # Seek @ EOF
    fsize = f.tell()        # Get Size
    f.seek (max (fsize-1024, 0), 0) # Set pos @ last n chars
    lines = f.readlines()       # Read to end

lines = lines[-10:]    # Get last 10 lines

# This returns True if any line is exactly find_str + "n"
print find_str + "n" in lines

# If you're searching for a substring
for line in lines:
    if find_str in line:
        print True
        break

  • Das “if len(l) < 10" ist überflüssig. "drucke l[:-10]" behandelt diesen Fall.

    – Darius Speck

    4. November ’08 um 0:35

  • @Darius: Ich meinte wirklich, wenn len(l) > 10, behoben

    – PabloG

    4. November ’08 um 1:16

  • Linien[:-10] lässt die letzten 10 Zeilen fallen. Was du willst, sind Linien[-10:].

    – Markus Jarderot

    4. November ’08 um 1:20

  • @MizardX / @ΤΖΩΤΖΙΟΥ: Du hast natürlich Recht. Thx für den Bugfix/Kommentar

    – PabloG

    4. November ’08 um 11:31

  • Dies schlägt fehl, wenn die Datei sehr lange Zeilen enthält. Der Code geht davon aus, dass die letzten 10 Zeilen innerhalb der letzten 1 KB an Daten liegen. Es sollte überprüft werden, ob mindestens 11 Zeilen vorhanden sind, oder weiter rückwärts suchen, bis diese Bedingung wahr ist.

    – Bryan Oakley

    13. März ’10 um 13:09

Hier ist eine Antwort wie die von MizardX, jedoch ohne das offensichtliche Problem, dass im schlimmsten Fall quadratische Zeit benötigt wird, um den Arbeitsstring wiederholt nach Zeilenumbrüchen zu durchsuchen, wenn Chunks hinzugefügt werden.

Im Vergleich zur Active State-Lösung (die ebenfalls quadratisch zu sein scheint) explodiert dies bei einer leeren Datei nicht und sucht pro gelesenem Block statt zwei.

Im Vergleich zum laichenden ‘Schwanz’ ist dies in sich abgeschlossen. (Aber ‘Schwanz’ ist am besten, wenn Sie ihn haben.)

Im Vergleich dazu, ein paar kB vom Ende zu nehmen und zu hoffen, dass es genug ist, funktioniert dies für jede Zeilenlänge.

import os

def reversed_lines(file):
    "Generate the lines of file in reverse order."
    part=""
    for block in reversed_blocks(file):
        for c in reversed(block):
            if c == 'n' and part:
                yield part[::-1]
                part=""
            part += c
    if part: yield part[::-1]

def reversed_blocks(file, blocksize=4096):
    "Generate blocks of file's contents in reverse order."
    file.seek(0, os.SEEK_END)
    here = file.tell()
    while 0 < here:
        delta = min(blocksize, here)
        here -= delta
        file.seek(here, os.SEEK_SET)
        yield file.read(delta)

Um es wie gewünscht zu verwenden:

from itertools import islice

def check_last_10_lines(file, key):
    for line in islice(reversed_lines(file), 10):
        if line.rstrip('n') == key:
            print 'FOUND'
            break

Bearbeiten: map() in itertools.imap() in head() geändert. Bearbeiten 2: vereinfachte reversed_blocks(). Bearbeiten 3: Vermeiden Sie das erneute Scannen des Schwanzes für Zeilenumbrüche. Bearbeiten 4: schrieb reversed_lines() um, weil str.splitlines() ein abschließendes ‘n’ ignoriert, wie BrianB bemerkte (danke).

Beachten Sie, dass in sehr alten Python-Versionen die String-Verkettung in einer Schleife hier quadratische Zeit in Anspruch nimmt. CPython aus den letzten Jahren vermeidet dieses Problem automatisch.

  • Sehr nett – ich habe mir die Liste der Antworten durchgelesen, bis ich hier ankam, da ich wusste, dass die beste diejenige sein würde, die versiert genug war, um sie zu verwenden yield Direktive

    – Brian B

    4. Dezember ’12 um 23:08

  • Ein Eckfall für dich wurde behoben – manchmal endet ein Block in einem Zeilenumbruch, sodass der Schwanz ein eigener Eintrag ist.

    – Brian B

    5. Dezember ’12 um 21:12

  • @BrianB, danke – können Sie einen Testfall geben, bei dem mein Code bricht? Ich habe Ihre Änderung rückgängig gemacht, weil sie beim ersten Versuch fehlgeschlagen ist, ‘nhallonnweltn’ (mit Blockgröße auf 2). (Mein Dank ist nicht ironisch, denn ich gehe davon aus, dass Sie einen echten Fall bemerkt haben, in dem mein Code stattdessen fehlgeschlagen ist.)

    – Darius Speck

    9. Dezember ’12 um 19:45

  • @BrianB, ich glaube, ich sehe, was Sie gesehen haben, und es scheint am schönsten zu sein, die gesamte Funktion leider neu zu schreiben. Fertig.

    – Darius Speck

    9. Dezember ’12 um 20:49

  • Funktioniert, ist aber etwa halb so schnell wie die vorherige Version (wie korrigiert).

    – Brian B

    13. Dezember ’12 um 15:02


Wenn Sie Python auf einem POSIX-System ausführen, können Sie ‘tail -10’ verwenden, um die letzten paar Zeilen abzurufen. Dies kann schneller sein, als Ihren eigenen Python-Code zu schreiben, um die letzten 10 Zeilen zu erhalten. Anstatt die Datei direkt zu öffnen, öffnen Sie eine Pipe mit dem Befehl ‘tail -10 filename’. Wenn Sie sich jedoch der Protokollausgabe sicher sind (z. B. wissen Sie, dass es noch nie irgendwelche sehr langen Zeilen, die Hunderte oder Tausende von Zeichen lang sind), dann wäre es in Ordnung, einen der aufgeführten Ansätze zum Lesen der letzten 2 KB zu verwenden.

  • Ich wäre damit vorsichtig, da Shell-Aufrufe viel mehr Overhead haben als ein direkter Zugriff.

    – Svante

    4. November ’08 um 5:35

  • Das ist ziemlich alt, aber ich habe eigentlich keinen Shell-Aufruf befürwortet. Ich habe empfohlen, das Skript mit einer Pipe-Ausgabe von tail aufzurufen, anstatt das Skript aufzurufen, um die gesamte Datei selbst zu lesen.

    – Myrddin Emrys

    22. März ’10 um 22:06

1642002846 437 Die effizienteste Methode um die letzten X Zeilen einer Datei
Ryan Ginstrom

Ich denke, das Lesen der letzten 2 KB der Datei sollte sicherstellen, dass Sie 10 Zeilen erhalten, und sollte nicht zu viel Ressourcenfresser sein.

file_handle = open("somefile")
file_size = file_handle.tell()
file_handle.seek(max(file_size - 2*1024, 0))

# this will get rid of trailing newlines, unlike readlines()
last_10 = file_handle.read().splitlines()[-10:]

assert len(last_10) == 10, "Only read %d lines" % len(last_10)

Hier ist eine Version mit mmap das scheint ziemlich effizient zu sein. Das große Plus ist das mmap verarbeitet automatisch die Auslagerungsanforderungen für die Datei zum Speicher für Sie.

import os
from mmap import mmap

def lastn(filename, n):
    # open the file and mmap it
    f = open(filename, 'r+')
    m = mmap(f.fileno(), os.path.getsize(f.name))

    nlcount = 0
    i = m.size() - 1 
    if m[i] == 'n': n += 1
    while nlcount < n and i > 0:
        if m[i] == 'n': nlcount += 1
        i -= 1
    if i > 0: i += 2

    return m[i:].splitlines()

target = "target string"
print [l for l in lastn('somefile', 10) if l == target]

  • Schön! Ich hätte an mmap denken sollen. Dies geht bei meinem Test einer wirklich großen 1-Zeilen-Datei jedoch um eine Größenordnung langsamer als meiner, denke ich, weil es im Python-Code Zeichen für Zeichen überprüft.

    – Darius Speck

    4. November ’08 um 5:57

  • Ja, ich war auch besorgt über die “reine Python” -Schleife. Die Schleife könnte möglicherweise effizienter gemacht werden als der von mir bereitgestellte Code. Wenn das mmap-Objekt eine rfind()-Methode hätte, hätte es viel besser sein können!

    – mhawke

    4. November ’08 um 23:00

  • Zu Ihrer Information: Die mmap-Objekte von Python v2.6.5 haben ein rfind() Methode.

    – RobM

    10. Feb. ’11 um 17:41

  • tail -r verwendet auch mmap für normale Dateien (siehe r_reg() Funktion)

    – jfs

    6. November ’13 um 5:45

1642002846 491 Die effizienteste Methode um die letzten X Zeilen einer Datei
Daryl Spitzer

Ich glaube, ich erinnere mich, den Code von . angepasst zu haben dieser Blogbeitrag von Manu Garg als ich etwas ähnliches machen musste.

  • Schön! Ich hätte an mmap denken sollen. Dies geht bei meinem Test einer wirklich großen 1-Zeilen-Datei jedoch um eine Größenordnung langsamer als meiner, denke ich, weil es im Python-Code Zeichen für Zeichen überprüft.

    – Darius Speck

    4. November ’08 um 5:57

  • Ja, ich war auch besorgt über die “reine Python” -Schleife. Die Schleife könnte möglicherweise effizienter gemacht werden als der von mir bereitgestellte Code. Wenn das mmap-Objekt eine rfind()-Methode hätte, hätte es viel besser sein können!

    – mhawke

    4. November ’08 um 23:00

  • Zu Ihrer Information: Die mmap-Objekte von Python v2.6.5 haben ein rfind() Methode.

    – RobM

    10. Feb. ’11 um 17:41

  • tail -r verwendet auch mmap für normale Dateien (siehe r_reg() Funktion)

    – jfs

    6. November ’13 um 5:45

1642002846 357 Die effizienteste Methode um die letzten X Zeilen einer Datei
Alex Coventry

Wenn Sie eine Unix-Box verwenden, os.popen("tail -10 " + filepath).readlines() wird wohl der schnellste Weg sein. Ansonsten hängt es davon ab, wie robust Sie es haben möchten. Die bisher vorgeschlagenen Methoden werden alle auf die eine oder andere Weise scheitern. Für Robustheit und Geschwindigkeit möchten Sie im häufigsten Fall wahrscheinlich so etwas wie eine logarithmische Suche: Verwenden Sie file.seek, um zum Ende der Datei minus 1000 Zeichen zu gehen, sie einzulesen, zu überprüfen, wie viele Zeilen sie enthält, dann zu EOF minus 3000 Zeichen , 2000 Zeichen einlesen, Zeilen zählen, dann EOF minus 7000, 4000 Zeichen einlesen, Zeilen zählen usw. bis Sie so viele Zeilen haben, wie Sie brauchen. Aber wenn Sie sicher sind, dass es immer auf Dateien mit vernünftigen Zeilenlängen ausgeführt wird, brauchen Sie das möglicherweise nicht.

Vielleicht finden Sie auch Inspiration in der Quellcode für das Unix tail Befehl.

.

460360cookie-checkDie effizienteste Methode, um die letzten X Zeilen einer Datei zu durchsuchen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy