Получить последние n строк файла, похожих на tail
Я пишу средство просмотра файлов журнала для веб-приложения, и для этого я хочу разбить файл журнала на страницы по строкам. Элементы в файле основаны на строках, с самым новым элементом внизу.
Итак, мне нужен tail()
метод, который может считывать n
строки снизу и поддерживать смещение. Это то, что я придумал.:
def tail(f, n, offset=0):
"""Reads a n lines from f with an offset of offset lines."""
avg_line_length = 74
to_read = n + offset
while 1:
try:
f.seek(-(avg_line_length * to_read), 2)
except IOError:
# woops. apparently file is smaller than what we want
# to step back, go to the beginning instead
f.seek(0)
pos = f.tell()
lines = f.read().splitlines()
if len(lines) >= to_read or pos == 0:
return lines[-to_read:offset and -offset or None]
avg_line_length *= 1.3
Разумный ли это подход? Какой рекомендуемый способ привязки файлов журнала к смещениям?
Переведено автоматически
Ответ 1
Это может быть быстрее, чем у вас. Не делает предположений о длине строки. Просматривает файл по одному блоку за раз, пока не будет найдено нужное количество символов '\n'.
def tail( f, lines=20 ):
total_lines_wanted = lines
BLOCK_SIZE = 1024
f.seek(0, 2)
block_end_byte = f.tell()
lines_to_go = total_lines_wanted
block_number = -1
blocks = [] # blocks of size BLOCK_SIZE, in reverse order starting
# from the end of the file
while lines_to_go > 0 and block_end_byte > 0:
if (block_end_byte - BLOCK_SIZE > 0):
# read the last block we haven't yet read
f.seek(block_number*BLOCK_SIZE, 2)
blocks.append(f.read(BLOCK_SIZE))
else:
# file too small, start from begining
f.seek(0,0)
# only read what was not read
blocks.append(f.read(block_end_byte))
lines_found = blocks[-1].count('\n')
lines_to_go -= lines_found
block_end_byte -= BLOCK_SIZE
block_number -= 1
all_read_text = ''.join(reversed(blocks))
return '\n'.join(all_read_text.splitlines()[-total_lines_wanted:])
Мне не нравятся сложные предположения о длине строки, когда - с практической точки зрения - вы никогда не сможете знать подобные вещи.
Как правило, это позволяет найти последние 20 строк при первом или втором прохождении цикла. Если ваш параметр из 74 символов действительно точен, вы задаете размер блока 2048 и почти сразу получаете 20 строк в конце.
Кроме того, я не трачу много мозговых калорий, пытаясь улучшить выравнивание с физическими блоками ОС. Используя эти высокоуровневые пакеты ввода-вывода, я сомневаюсь, что вы увидите какие-либо последствия для производительности от попыток выровнять границы блоков операционной системы. Если вы используете ввод-вывод более низкого уровня, вы можете увидеть ускорение.
Обновить
для Python 3.2 и выше следуйте процессу в байтах, как в текстовых файлах (те, которые открыты без "b" в строке режима), разрешен поиск только относительно начала файла (исключением является поиск до самого конца файла с помощью seek(0, 2)).:
например: f = open('C:/.../../apache_logs.txt', 'rb')
def tail(f, lines=20):
total_lines_wanted = lines
BLOCK_SIZE = 1024
f.seek(0, 2)
block_end_byte = f.tell()
lines_to_go = total_lines_wanted
block_number = -1
blocks = []
while lines_to_go > 0 and block_end_byte > 0:
if (block_end_byte - BLOCK_SIZE > 0):
f.seek(block_number*BLOCK_SIZE, 2)
blocks.append(f.read(BLOCK_SIZE))
else:
f.seek(0,0)
blocks.append(f.read(block_end_byte))
lines_found = blocks[-1].count(b'\n')
lines_to_go -= lines_found
block_end_byte -= BLOCK_SIZE
block_number -= 1
all_read_text = b''.join(reversed(blocks))
return b'\n'.join(all_read_text.splitlines()[-total_lines_wanted:])
Ответ 2
Предполагается, что unix-подобная система на Python 2 позволяет выполнять:
import os
def tail(f, n, offset=0):
stdin,stdout = os.popen2("tail -n "+n+offset+" "+f)
stdin.close()
lines = stdout.readlines(); stdout.close()
return lines[:,-offset]
Для python 3 вы можете сделать:
import subprocess
def tail(f, n, offset=0):
proc = subprocess.Popen(['tail', '-n', n + offset, f], stdout=subprocess.PIPE)
lines = proc.stdout.readlines()
return lines[:, -offset]
Ответ 3
Вот мой ответ. Чистый python. Используя timeit, это кажется довольно быстрым. Заполнение 100 строк файла журнала, состоящего из 100 000 строк:
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=10)
0.0014600753784179688
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=100)
0.00899195671081543
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=1000)
0.05842900276184082
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=10000)
0.5394978523254395
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=100000)
5.377126932144165
Вот код:
import os
def tail(f, lines=1, _buffer=4098):
"""Tail a file and get X lines from the end"""
# place holder for the lines found
lines_found = []
# block counter will be multiplied by buffer
# to get the block size from the end
block_counter = -1
# loop until we find X lines
while len(lines_found) < lines:
try:
f.seek(block_counter * _buffer, os.SEEK_END)
except IOError: # either file is too small, or too many lines requested
f.seek(0)
lines_found = f.readlines()
break
lines_found = f.readlines()
# we found enough lines, get out
# Removed this line because it was redundant the while will catch
# it, I left it for history
# if len(lines_found) > lines:
# break
# decrement the block counter to get the
# next X bytes
block_counter -= 1
return lines_found[-lines:]
Ответ 4
Если чтение всего файла допустимо, используйте deque.
from collections import deque
deque(f, maxlen=n)
До версии 2.6 в deques не было опции maxlen, но ее достаточно легко реализовать.
import itertools
def maxque(items, size):
items = iter(items)
q = deque(itertools.islice(items, size))
for item in items:
del q[0]
q.append(item)
return q
Если требуется прочитать файл с конца, то используйте галлопический (он же экспоненциальный) поиск.
def tail(f, n):
assert n >= 0
pos, lines = n+1, []
while len(lines) <= n:
try:
f.seek(-pos, 2)
except IOError:
f.seek(0)
break
finally:
lines = list(f)
pos *= 2
return lines[-n:]