Получить последние n строк файла с Python, похожего на tail - PullRequest
167 голосов
/ 26 сентября 2008

Я пишу средство просмотра файла журнала для веб-приложения, и для этого я хочу разбить на страницы строки файла журнала. Элементы в файле являются строками, основанными на самом новом элементе внизу.

Так что мне нужен метод 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

Это разумный подход? Каков рекомендуемый способ подгонки файлов журнала со смещением?

Ответы [ 30 ]

3 голосов
/ 30 августа 2017

Обновление решения @papercrane до python3. Откройте файл с помощью open(filename, 'rb') и:

def tail(f, window=20):
    """Returns the last `window` lines of file `f` as a list.
    """
    if window == 0:
        return []

    BUFSIZ = 1024
    f.seek(0, 2)
    remaining_bytes = f.tell()
    size = window + 1
    block = -1
    data = []

    while size > 0 and remaining_bytes > 0:
        if remaining_bytes - BUFSIZ > 0:
            # Seek back one whole BUFSIZ
            f.seek(block * BUFSIZ, 2)
            # read BUFFER
            bunch = f.read(BUFSIZ)
        else:
            # file too small, start from beginning
            f.seek(0, 0)
            # only read what was not read
            bunch = f.read(remaining_bytes)

        bunch = bunch.decode('utf-8')
        data.insert(0, bunch)
        size -= bunch.count('\n')
        remaining_bytes -= BUFSIZ
        block -= 1

    return ''.join(data).splitlines()[-window:]
2 голосов
/ 11 июня 2010

основано на ответе С. Лотта, получившем наибольшее количество голосов (25 сентября 2008 г. в 21:43), но исправлено для небольших файлов.

def tail(the_file, lines_2find=20):  
    the_file.seek(0, 2)                         #go to end of file
    bytes_in_file = the_file.tell()             
    lines_found, total_bytes_scanned = 0, 0
    while lines_2find+1 > lines_found and bytes_in_file > total_bytes_scanned: 
        byte_block = min(1024, bytes_in_file-total_bytes_scanned)
        the_file.seek(-(byte_block+total_bytes_scanned), 2)
        total_bytes_scanned += byte_block
        lines_found += the_file.read(1024).count('\n')
    the_file.seek(-total_bytes_scanned, 2)
    line_list = list(the_file.readlines())
    return line_list[-lines_2find:]

    #we read at least 21 line breaks from the bottom, block by block for speed
    #21 to ensure we don't get a half line

Надеюсь, это полезно.

2 голосов
/ 06 октября 2012

Существует несколько существующих реализаций tail на pypi, которые вы можете установить с помощью pip:

  • mtFileUtil
  • multitail
  • log4tailer
  • ...

В зависимости от вашей ситуации, использование одного из этих существующих инструментов может иметь преимущества.

2 голосов
/ 19 июня 2016

Вот довольно простая реализация:

with open('/etc/passwd', 'r') as f:
  try:
    f.seek(0,2)
    s = ''
    while s.count('\n') < 11:
      cur = f.tell()
      f.seek((cur - 10))
      s = f.read(10) + s
      f.seek((cur - 10))
    print s
  except Exception as e:
    f.readlines()
1 голос
/ 12 апреля 2011

Некоторые из этих решений имеют проблемы, если файл не заканчивается на \ n или не гарантирует, что прочитана вся первая строка.

def tail(file, n=1, bs=1024):
    f = open(file)
    f.seek(-1,2)
    l = 1-f.read(1).count('\n') # If file doesn't end in \n, count it anyway.
    B = f.tell()
    while n >= l and B > 0:
            block = min(bs, B)
            B -= block
            f.seek(B, 0)
            l += f.read(block).count('\n')
    f.seek(B, 0)
    l = min(l,n) # discard first (incomplete) line if l > n
    lines = f.readlines()[-l:]
    f.close()
    return lines
1 голос
/ 26 сентября 2008

Для повышения эффективности работы с очень большими файлами (обычно в ситуациях с лог-файлами, когда вы можете использовать tail), вы, как правило, хотите избегать чтения всего файла (даже если вы делаете это, не считывая весь файл сразу в память) Вам нужно как-то отработать смещение в строках, а не в символах. Одной из возможностей является чтение в обратном порядке с помощью функции seek () char за char, но это очень медленно. Вместо этого лучше обрабатывать в больших блоках.

У меня есть служебная функция, которую я недавно написал для чтения файлов в обратном направлении, которую можно использовать здесь.

import os, itertools

def rblocks(f, blocksize=4096):
    """Read file as series of blocks from end of file to start.

    The data itself is in normal order, only the order of the blocks is reversed.
    ie. "hello world" -> ["ld","wor", "lo ", "hel"]
    Note that the file must be opened in binary mode.
    """
    if 'b' not in f.mode.lower():
        raise Exception("File must be opened using binary mode.")
    size = os.stat(f.name).st_size
    fullblocks, lastblock = divmod(size, blocksize)

    # The first(end of file) block will be short, since this leaves 
    # the rest aligned on a blocksize boundary.  This may be more 
    # efficient than having the last (first in file) block be short
    f.seek(-lastblock,2)
    yield f.read(lastblock)

    for i in range(fullblocks-1,-1, -1):
        f.seek(i * blocksize)
        yield f.read(blocksize)

def tail(f, nlines):
    buf = ''
    result = []
    for block in rblocks(f):
        buf = block + buf
        lines = buf.splitlines()

        # Return all lines except the first (since may be partial)
        if lines:
            result.extend(lines[1:]) # First line may not be complete
            if(len(result) >= nlines):
                return result[-nlines:]

            buf = lines[0]

    return ([buf]+result)[-nlines:]


f=open('file_to_tail.txt','rb')
for line in tail(f, 20):
    print line

[Редактировать] Добавлена ​​более конкретная версия (избегать необходимости дважды)

1 голос
/ 09 ноября 2010

вы можете перейти в конец вашего файла с помощью f.seek (0, 2), а затем прочитать строки одну за другой со следующей заменой readline ():

def readline_backwards(self, f):
    backline = ''
    last = ''
    while not last == '\n':
        backline = last + backline
        if f.tell() <= 0:
            return backline
        f.seek(-1, 1)
        last = f.read(1)
        f.seek(-1, 1)
    backline = last
    last = ''
    while not last == '\n':
        backline = last + backline
        if f.tell() <= 0:
            return backline
        f.seek(-1, 1)
        last = f.read(1)
        f.seek(-1, 1)
    f.seek(1, 1)
    return backline
1 голос
/ 20 января 2011

На основе ответа Eyecue (10 июня '10 в 21:28): этот класс добавляет метод head () и tail () к объекту файла.

class File(file):
    def head(self, lines_2find=1):
        self.seek(0)                            #Rewind file
        return [self.next() for x in xrange(lines_2find)]

    def tail(self, lines_2find=1):  
        self.seek(0, 2)                         #go to end of file
        bytes_in_file = self.tell()             
        lines_found, total_bytes_scanned = 0, 0
        while (lines_2find+1 > lines_found and
               bytes_in_file > total_bytes_scanned): 
            byte_block = min(1024, bytes_in_file-total_bytes_scanned)
            self.seek(-(byte_block+total_bytes_scanned), 2)
            total_bytes_scanned += byte_block
            lines_found += self.read(1024).count('\n')
        self.seek(-total_bytes_scanned, 2)
        line_list = list(self.readlines())
        return line_list[-lines_2find:]

Использование:

f = File('path/to/file', 'r')
f.head(3)
f.tail(3)
1 голос
/ 15 мая 2019

Простой:

with open("test.txt") as f:
data = f.readlines()
tail = data[-2:]
print(''.join(tail)
0 голосов
/ 19 декабря 2018

Существует очень полезный модуль , который может сделать это:

from file_read_backwards import FileReadBackwards

with FileReadBackwards("/tmp/file", encoding="utf-8") as frb:

# getting lines by lines starting from the last line up
for l in frb:
    print(l)
...