Щоб прочитати перший і останній рядки файлу, ви можете ...
- відкрити файл, ...
- ... прочитати перший рядок, використовуючи вбудований
readline()
, ...
- ... шукати (рухати курсор) до кінця файлу, ...
- ... крокуйте назад, поки не зустрінете EOL (розрив рядка) і ...
- ... прочитайте звідти останній рядок.
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)
Перейти безпосередньо до другого останнього байта, щоб запобігти поверненню символів нового рядка до порожніх рядків *.
Поточне зміщення зміщується вперед на одиницю кожного разу, коли читається байт, тому крок назад здійснюється за два байти за раз, минувши недавно прочитаний байт і байт для читання наступного.
whence
Рухаючись параметр fseek(offset, whence=0)
вказує , що fseek
слід прагнути до позиції offset
байтів по відношенню до ...
* Як і слід було очікувати, оскільки поведінка за замовчуванням для більшості додатків, включаючи print
і echo
, додає по одному до кожного написаного рядка і не впливає на рядки, що відсутній у символі нового рядка.
Ефективність
1-2 мільйони рядків кожен, і я повинен зробити це для кількох сотень файлів.
Я приурочив цей метод до часу і порівняв його з найпопулярнішою відповіддю.
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.
Мільйони ліній призведе до збільшення різниці набагато більше.
Точний код, який використовується для хронометражу:
with open(file, "rb") as f:
first = f.readline()
for last in f: pass
Поправка
Складніший і важчий для читання варіант розгляду коментарів та проблем, порушених з того часу.
Також додана підтримка мультібайтних роздільників, readlast(b'X<br>Y', b'<br>', fixed=False)
.
Зверніть увагу, що ця варіація дійсно повільна для великих файлів через не відносні зміщення, необхідні в текстовому режимі. Змініть свої потреби або взагалі не використовуйте їх, оскільки вам, мабуть, краще використовувати f.readlines()[-1]
файли, відкриті в текстовому режимі.
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="")