Um sowohl die erste als auch die letzte Zeile einer Datei zu lesen, können Sie ...
- öffne die Datei, ...
- ... lesen Sie die erste Zeile mit eingebauten
readline()
, ...
- ... suchen (den Cursor bewegen) bis zum Ende der Datei, ...
- ... treten Sie zurück, bis Sie auf EOL (Zeilenumbruch) stoßen und ...
- ... lesen Sie die letzte Zeile von dort.
def readlastline(f):
f.seek(-2, 2)
while f.read(1) != b"\n":
f.seek(-2, 1)
return f.read()
with open(file, "rb") as f:
first = f.readline()
last = readlastline(f)
Wechsel zu dem zweiten letzten Byte unmittelbar nachlaufZeilenUmbrüche zu verhindern Leerzeilen zu bewirken , zurückgeführt wird *.
Der aktuelle Offset wird jedes Mal, wenn ein Byte gelesen wird, um eins vorgeschoben, sodass der Rückschritt zwei Bytes gleichzeitig erfolgt, nach dem zuletzt gelesenen Byte und dem als nächstes zu lesenden Byte.
Der übergebene whence
Parameter gibt an, fseek(offset, whence=0)
dass fseek
nach Positionsbytes offset
relativ zu ... gesucht werden soll.
* Wie zu erwarten ist, besteht das Standardverhalten der meisten Anwendungen, einschließlich print
und echo
, darin, an jede geschriebene Zeile eine anzuhängen, und hat keine Auswirkung auf Zeilen, denen ein nachfolgendes Zeilenumbruchzeichen fehlt.
Effizienz
Jeweils 1-2 Millionen Zeilen und ich muss dies für mehrere hundert Dateien tun.
Ich habe diese Methode zeitlich festgelegt und mit der Top-Antwort verglichen.
10k iterations processing a file of 6k lines totalling 200kB: 1.62s vs 6.92s.
100 iterations processing a file of 6k lines totalling 1.3GB: 8.93s vs 86.95.
Millionen von Zeilen würden den Unterschied viel mehr erhöhen .
Exakt-Code für das Timing:
with open(file, "rb") as f:
first = f.readline()
for last in f: pass
Änderung
Eine komplexere und schwerer zu lesende Variante, um Kommentare und Probleme anzusprechen, die seitdem aufgeworfen wurden.
Fügt auch Unterstützung für Multibyte-Trennzeichen hinzu readlast(b'X<br>Y', b'<br>', fixed=False)
.
Bitte beachten Sie, dass diese Variation für große Dateien aufgrund der im Textmodus erforderlichen nicht relativen Offsets sehr langsam ist . Passen Sie es an Ihre Bedürfnisse an oder verwenden Sie es überhaupt nicht, da Sie es wahrscheinlich besser verwenden, wenn Sie f.readlines()[-1]
Dateien im Textmodus öffnen.
from os import SEEK_END
def readlast(f, sep, fixed=True):
r"""Read the last segment from a file-like object.
:param f: File to read last line from.
:type f: file-like object
:param sep: Segment separator (delimiter).
:type sep: bytes, str
:param fixed: Treat data in ``f`` as a chain of fixed size blocks.
:type fixed: bool
:returns: Last line of file.
:rtype: bytes, str
"""
bs = len(sep)
step = bs if fixed else 1
if not bs:
raise ValueError("Zero-length separator.")
try:
o = f.seek(0, SEEK_END)
o = f.seek(o-bs-step)
while f.read(bs) != sep:
o = f.seek(o-step)
except (OSError,ValueError):
f.seek(0)
return f.read()
def test_readlast():
from io import BytesIO, StringIO
f = StringIO("first\nlast\n")
assert readlast(f, "\n") == "last\n"
f = BytesIO(b'first|last')
assert readlast(f, b'|') == b'last'
f = BytesIO("X\nY\n".encode("utf-8"))
assert readlast(f, b'\n').decode() == "Y\n"
f = BytesIO("X\nY\n".encode("utf-16"))
assert readlast(f, b'\n\x00').decode('utf-16') == "Y\n"
f = BytesIO("X\nY\n".encode("utf-32"))
assert readlast(f, b'\n\x00\x00\x00').decode('utf-32') == "Y\n"
f = StringIO("X<br>Y")
assert readlast(f, "<br>", fixed=False) == "Y"
seps = { 'utf8': b'\n', 'utf16': b'\n\x00', 'utf32': b'\n\x00\x00\x00' }
assert "\n".encode('utf8' ) == seps['utf8']
assert "\n".encode('utf16')[2:] == seps['utf16']
assert "\n".encode('utf32')[4:] == seps['utf32']
edges = (
("" , "" ),
("X" , "X" ),
("\n" , "\n"),
("\n\n", "\n"),
(b'\n\xe2\x9c\x8a\n'.decode(), b'\xe2\x9c\x8a\n'.decode()),
)
for txt, match in edges:
for enc,sep in seps.items():
assert readlast(BytesIO(txt.encode(enc)), sep).decode(enc) == match
if __name__ == "__main__":
import sys
for path in sys.argv[1:]:
with open(path) as f:
print(f.readline() , end="")
print(readlast(f,"\n"), end="")