Отримайте останні n рядків файлу, схожі на хвіст


181

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

Тому мені потрібен 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

Це розумний підхід? Який рекомендований спосіб усунення файлів журналу хвостиком із зміщенням?


У моїй системі (linux SLES 10) пошук по відношенню до кінця викликає IOError "не може робити ненульових пошуків відносного кінця". Мені подобається це рішення, але я його змінив, щоб отримати довжину файлу ( seek(0,2)тоді tell()), і використовую це значення для пошуку відносно початку.
Енн

2
Поздравляю - це питання ввійшло до вихідного коду Кіппо
Майлз

Параметри openкоманди , використовуваної для створення fоб'єкта файлу має бути вказаний, так як в залежності від того f=open(..., 'rb')чи повинні бути оброблені по- іншомуf=open(..., 'rt')f
Ігор Fobia

Відповіді:


123

Це може бути швидше, ніж ваше. Не передбачає припущень щодо довжини рядка. Реєструється через файл один блок за часом, поки не буде знайдено потрібну кількість символів \ \ 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" у рядку режимів), дозволено шукати лише відносно початку файлу (виняток шукає до самого кінця файлу з пошуком (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:])

13
Це не вдається для малих логінфілів - IOError: недійсний аргумент - f.seek (блок * 1024, 2)
охноє

1
Справді дуже приємний підхід. Я використав трохи змінену версію коду вище і придумав цей рецепт: code.activestate.com/recipes/577968-log-watcher-tail-f-log
Giampaolo Rodolà

6
Більше не працює в python 3.2. Я отримую, io.UnsupportedOperation: can't do nonzero end-relative seeksщо можу змінити зміщення на 0, але це перешкоджає призначенню функції.
Логічна помилка

4
@DavidEnglund Причина тут . Якщо коротко: пошук тексту щодо кінця файлу не дозволений у текстовому режимі, імовірно, тому, що вміст файлу повинен бути декодований, і, як правило, пошук довільної позиції у послідовності закодованих байтів може мати невизначені результати, коли ви спроба декодування Unicode, починаючи з цього положення. Пропозиція, що пропонується за посиланням, - спробувати відкрити файл у двійковому режимі та виконати розшифровку самостійно, уловивши винятки DecodeError.
макс

6
НЕ ВИКОРИСТОВУЙТЕ ЦІЙ КОД. Він псує лінії в деяких прикордонних випадках у пітоні 2.7. Відповідь від @papercrane нижче виправляє.
xApple

88

Передбачає 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]

5
Повинна бути незалежною від платформи. Крім того, якщо ви прочитаєте питання, то побачите, що f - файл, подібний до об'єкта.
Армін Ронахер

40
питання не говорить про те, що залежність від платформи неприйнятна. я не розумію, чому це заслуговує двох зворотних записів, коли він забезпечує дуже unixy (можливо, те, що ви шукаєте ... безумовно, був для мене) спосіб робити саме те, що задається питанням.
Шабіроб

3
Дякую, я думав, що мені доведеться вирішити це в чистому Python, але немає причин не використовувати утиліти UNIX, коли вони є під рукою, тому я пішов із цим. FWIW в сучасному Python, subprocess.check_output, ймовірно, кращий порівняно з os.popen2; це трохи спрощує речі, оскільки він просто повертає результат у вигляді рядка та збільшує ненульовий код виходу.
mrooney

3
Хоча це залежить від платформи, це дуже ефективний спосіб робити те, що було запропоновано, а також надзвичайно швидкий спосіб зробити це (Вам не потрібно завантажувати весь файл у пам'ять). @Shabbyrobe
earthmeLon

6
Ви можете offset_total = str(n+offset)stdin,stdout = os.popen2("tail -n "+offset_total+" "+f)TypeErrors (cannot concatenate int+str)
заздалегідь розрахувати

32

Ось моя відповідь. Чистий пітон. Використання 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:]

3
Елегантне рішення! Чи if len(lines_found) > lines:справді потрібно? Чи не вдалося б це loopсприйняти також?
Максиміліан Пітерс

Питання для мого розуміння: os.SEEK_ENDвикористовується просто для ясності? Наскільки я виявив, його значення постійне (= 2). Мені було цікаво про те, щоб залишити це, щоб мати можливість залишити його import os. Дякую за чудове рішення!
n1k31t4

2
@MaximilianPeters так. Це не обов'язково. Я прокоментував це.
glenbot

@DexterMorgan ви можете замінити os.SEEK_ENDйого цілим числовим еквівалентом. Це було головним чином для читабельності.
glenbot

1
Я схвалив, але малий ніт. Після пошуку, прочитаний перший рядок може бути неповним, тому для отримання N _complete_lines я змінив while len(lines_found) < linesна while len(lines_found) <= linesв своїй копії. Дякую!
Грем Клін

30

Якщо читати весь файл є прийнятним, використовуйте deque.

from collections import deque
deque(f, maxlen=n)

До 2.6 в деках не було варіантів 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:]

Чому ця нижня функція працює? pos *= 2здається абсолютно довільним. Яке його значення?
2мак

1
@ 2mac Експонентний пошук . Він читається з кінця файлу ітеративно, подвоюючи кількість прочитаного щоразу, поки не знайдеться достатня кількість рядків.
А. Коаді

Я думаю, що рішення для читання з кінця не підтримуватиме файли, кодовані UTF-8, оскільки довжина символів є змінною, і ви можете (швидше за все,) приземлитися з деяким дивним зміщенням, яке не можна правильно інтерпретувати.
Майк

на жаль, ваше скачуюче рішення пошуку не працює для python 3. Оскільки f.seek () не сприймає негативного зміщення. Я оновив ваш код, щоб він працював на python 3 посилання
itsjwala

25

Відповідь С.Лотта вище майже працює для мене, але в кінцевому підсумку дає мені часткові рядки. Виявляється, він пошкоджує дані на межах блоків, оскільки дані утримують блоки зчитування у зворотному порядку. Коли "" .join (дані) викликається, блоки перебувають у неправильному порядку. Це виправляє це.

def tail(f, window=20):
    """
    Returns the last `window` lines of file `f` as a list.
    f - a byte file-like object
    """
    if window == 0:
        return []
    BUFSIZ = 1024
    f.seek(0, 2)
    bytes = f.tell()
    size = window + 1
    block = -1
    data = []
    while size > 0 and bytes > 0:
        if bytes - BUFSIZ > 0:
            # Seek back one whole BUFSIZ
            f.seek(block * BUFSIZ, 2)
            # read BUFFER
            data.insert(0, f.read(BUFSIZ))
        else:
            # file too small, start from begining
            f.seek(0,0)
            # only read what was not read
            data.insert(0, f.read(bytes))
        linesFound = data[0].count('\n')
        size -= linesFound
        bytes -= BUFSIZ
        block -= 1
    return ''.join(data).splitlines()[-window:]

1
Вставлення на початку списку - погана ідея. Чому б не використовувати структуру deque?
Сергій11г

1
На жаль, не сумісний Python 3 ... намагаюся з'ясувати, чому.
Шерлок70

20

Код, який я закінчив використовувати. Я думаю, що це найкраще поки що:

def tail(f, n, offset=None):
    """Reads a n lines from f with an offset of offset lines.  The return
    value is a tuple in the form ``(lines, has_more)`` where `has_more` is
    an indicator that is `True` if there are more lines in the file.
    """
    avg_line_length = 74
    to_read = n + (offset or 0)

    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], \
                   len(lines) > to_read or pos > 0
        avg_line_length *= 1.3

5
точно не відповідає на питання.
sheki

13

Просте і швидке рішення з mmap:

import mmap
import os

def tail(filename, n):
    """Returns last n lines from the filename. No exception handling"""
    size = os.path.getsize(filename)
    with open(filename, "rb") as f:
        # for Windows the mmap parameters are different
        fm = mmap.mmap(f.fileno(), 0, mmap.MAP_SHARED, mmap.PROT_READ)
        try:
            for i in xrange(size - 1, -1, -1):
                if fm[i] == '\n':
                    n -= 1
                    if n == -1:
                        break
            return fm[i + 1 if i else 0:].splitlines()
        finally:
            fm.close()

1
Це, мабуть, найшвидша відповідь, коли вхід може бути величезним (або це було б, якби він використовував .rfindметод для сканування назад на нові рядки, а не виконує байт під час перевірки на рівні Python; в CPython, замінюючи код рівня Python на C вбудовані дзвінки зазвичай виграють на багато). Що стосується менших входів, то dequeз a maxlenє більш простим і, ймовірно, так само швидким.
ShadowRanger

4

Ще більш чиста версія, сумісна з python3, яка не вставляє, а додає та повертає:

def tail(f, window=1):
    """
    Returns the last `window` lines of file `f` as a list of bytes.
    """
    if window == 0:
        return b''
    BUFSIZE = 1024
    f.seek(0, 2)
    end = f.tell()
    nlines = window + 1
    data = []
    while nlines > 0 and end > 0:
        i = max(0, end - BUFSIZE)
        nread = min(end, BUFSIZE)

        f.seek(i)
        chunk = f.read(nread)
        data.append(chunk)
        nlines -= chunk.count(b'\n')
        end -= nread
    return b'\n'.join(b''.join(reversed(data)).splitlines()[-window:])

використовуйте його так:

with open(path, 'rb') as f:
    last_lines = tail(f, 3).decode('utf-8')

Не надто пошарпаний - але я б взагалі порадив не додавати відповіді на 10-річне запитання з великою кількістю відповідей. Але допоможіть мені: що конкретно для Python 3 у вашому коді?
usr2564301

Інші відповіді були точно працює добре :-) PY3: см stackoverflow.com/questions/136168 / ...
Hauke Rehfeld

3

Оновіть рішення @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:]

3

Опублікувавши відповідь за бажанням коментаторів на мою відповідь на подібне запитання, де та сама техніка була використана для мутації останнього рядка файлу, а не просто його отримання.

Для файлу значного розміру mmapце найкращий спосіб зробити це. Щоб покращити існуючу mmapвідповідь, ця версія є портативною між Windows та Linux, і вона повинна працювати швидше (хоча вона не працюватиме без деяких модифікацій на 32-бітному Python з файлами в діапазоні ГБ, див. Іншу відповідь для підказок щодо поводження з цим , і для модифікації для роботи на Python 2 ).

import io  # Gets consistent version of open for both Py2.7 and Py3.x
import itertools
import mmap

def skip_back_lines(mm, numlines, startidx):
    '''Factored out to simplify handling of n and offset'''
    for _ in itertools.repeat(None, numlines):
        startidx = mm.rfind(b'\n', 0, startidx)
        if startidx < 0:
            break
    return startidx

def tail(f, n, offset=0):
    # Reopen file in binary mode
    with io.open(f.name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # len(mm) - 1 handles files ending w/newline by getting the prior line
        startofline = skip_back_lines(mm, offset, len(mm) - 1)
        if startofline < 0:
            return []  # Offset lines consumed whole file, nothing to return
            # If using a generator function (yield-ing, see below),
            # this should be a plain return, no empty list

        endoflines = startofline + 1  # Slice end to omit offset lines

        # Find start of lines to capture (add 1 to move from newline to beginning of following line)
        startofline = skip_back_lines(mm, n, startofline) + 1

        # Passing True to splitlines makes it return the list of lines without
        # removing the trailing newline (if any), so list mimics f.readlines()
        return mm[startofline:endoflines].splitlines(True)
        # If Windows style \r\n newlines need to be normalized to \n, and input
        # is ASCII compatible, can normalize newlines with:
        # return mm[startofline:endoflines].replace(os.linesep.encode('ascii'), b'\n').splitlines(True)

Звідси випливає, що кількість рядків у рядках є досить малим, щоб ви могли спокійно прочитати їх усі в пам'яті відразу; Ви також можете зробити цю функцію генератора і вручну прочитати рядок, замінивши підсумковий рядок на:

        mm.seek(startofline)
        # Call mm.readline n times, or until EOF, whichever comes first
        # Python 3.2 and earlier:
        for line in itertools.islice(iter(mm.readline, b''), n):
            yield line

        # 3.3+:
        yield from itertools.islice(iter(mm.readline, b''), n)

Нарешті, це читається у двійковому режимі (необхідне для використання mmap), тому він дає strрядки (Py2) та bytesрядки (Py3); якщо ви хочете unicode(Py2) або str(Py3), ітеративний підхід може бути налаштований для декодування для вас та / або виправлення нових рядків:

        lines = itertools.islice(iter(mm.readline, b''), n)
        if f.encoding:  # Decode if the passed file was opened with a specific encoding
            lines = (line.decode(f.encoding) for line in lines)
        if 'b' not in f.mode:  # Fix line breaks if passed file opened in text mode
            lines = (line.replace(os.linesep, '\n') for line in lines)
        # Python 3.2 and earlier:
        for line in lines:
            yield line
        # 3.3+:
        yield from lines

Примітка: я все це набрав на машині, де мені не вистачає доступу до Python для тестування. Будь ласка, дайте мені знати, якщо я щось надрукував; це було досить подібне до моєї іншої відповіді, що я думаю, що це має спрацювати, але налаштування (наприклад, обробка ан offset) може призвести до тонких помилок. Будь ласка, повідомте мене в коментарях, якщо є якісь помилки.


3

Я знайшов Попена вище як найкраще рішення. Це швидко і брудно, і він працює для python 2.6 на Unix-машині, я використав наступне

def GetLastNLines(self, n, fileName):
    """
    Name:           Get LastNLines
    Description:        Gets last n lines using Unix tail
    Output:         returns last n lines of a file
    Keyword argument:
    n -- number of last lines to return
    filename -- Name of the file you need to tail into
    """
    p = subprocess.Popen(['tail','-n',str(n),self.__fileName], stdout=subprocess.PIPE)
    soutput, sinput = p.communicate()
    return soutput

soutput буде містити останні n рядків коду. перебирати через вихідний рядок за рядком робити:

for line in GetLastNLines(50,'myfile.log').split('\n'):
    print line

2

на основі голосової відповіді С.Лотта (25 вересня08 р. о 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

Є кілька існуючих реалізацій хвоста на pypi, який можна встановити за допомогою pip:

  • mtFileUtil
  • багаторічний
  • log4tailer
  • ...

Залежно від вашої ситуації, можливо, скористайтеся одним із цих існуючих інструментів.


Чи знаєте ви про будь-який модуль, який працює в Windows? Я намагався tailhead, tailerале вони не спрацювали. Також пробували mtFileUtil. Спочатку це було помилкою, оскільки у printтвердженнях не було дужок (я на Python 3.6). Я додав ці дані, reverse.pyі повідомлення про помилки не було, але коли мій скрипт викликає модуль ( mtFileUtil.tail(open(logfile_path), 5)), він нічого не друкує.
Technext

2

Простий:

with open("test.txt") as f:
data = f.readlines()
tail = data[-2:]
print(''.join(tail)

Це абсолютно погана реалізація. Розгляньте обробку величезних файлів, і де n теж величезна, занадто дорога операція
Nivesh Krishna

1

Для ефективності використання дуже великих файлів (звичайно в ситуаціях реєстрації файлів, коли ви можете використовувати хвостик), як правило, ви хочете уникати читання всього файлу (навіть якщо ви робите це, не читаючи відразу весь файл у пам'яті). потрібно якось відпрацювати зміщення в рядках, а не символах. Однією з можливостей є читання назад за допомогою char () char by 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

ви можете перейти до кінця файлу за допомогою 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

На основі відповіді 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

Деякі з цих рішень мають проблеми, якщо файл не закінчується \ 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

Ось досить проста реалізація:

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()

Чудовий приклад! Не могли б ви пояснити використання спробувати перед f.seek? Чому б не перед with open? Крім того, чому exceptви робите f.readlines()??

Чесно кажучи, спроба, ймовірно, повинна початись першою. Я не пам’ятаю, щоб мати причину не ловити open (), окрім як на здоровій стандартній системі Linux, / etc / passwd завжди має бути читабельним. спробуйте, тоді з - це звичайніший порядок.
GL2014

1

Є дуже корисний модуль, який може це зробити:

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)

1

Ще одне рішення

якщо ваш txt файл виглядає так: миша змія кішка ящірка вовча собака

Ви можете змінити цей файл, просто скориставшись індексуванням масиву в python '' '

contents=[]
def tail(contents,n):
    with open('file.txt') as file:
        for i in file.readlines():
            contents.append(i)

    for i in contents[:n:-1]:
        print(i)

tail(contents,-5)

результат: собака вовк ящірка кішка


1

Найпростіший спосіб - це використовувати deque:

from collections import deque

def tail(filename, n=10):
    with open(filename) as f:
        return deque(f, n)

0

Мені довелося прочитати конкретне значення з останнього рядка файлу і натрапив на цей потік. Замість того, щоб винаходити колесо в Python, я закінчився крихітним скриптом оболонки, збереженим як / usr / local / bin / get_last_netp:

#! /bin/bash
tail -n1 /home/leif/projects/transfer/export.log | awk {'print $14'}

А в програмі Python:

from subprocess import check_output

last_netp = int(check_output("/usr/local/bin/get_last_netp"))

0

Не перший приклад використання декету, а більш простий. Цей загальний: він працює на будь-якому ітерабельному об’єкті, а не лише на файлі.

#!/usr/bin/env python
import sys
import collections
def tail(iterable, N):
    deq = collections.deque()
    for thing in iterable:
        if len(deq) >= N:
            deq.popleft()
        deq.append(thing)
    for thing in deq:
        yield thing
if __name__ == '__main__':
    for line in tail(sys.stdin,10):
        sys.stdout.write(line)

0
This is my version of tailf

import sys, time, os

filename = 'path to file'

try:
    with open(filename) as f:
        size = os.path.getsize(filename)
        if size < 1024:
            s = size
        else:
            s = 999
        f.seek(-s, 2)
        l = f.read()
        print l
        while True:
            line = f.readline()
            if not line:
                time.sleep(1)
                continue
            print line
except IOError:
    pass

0
import time

attemps = 600
wait_sec = 5
fname = "YOUR_PATH"

with open(fname, "r") as f:
    where = f.tell()
    for i in range(attemps):
        line = f.readline()
        if not line:
            time.sleep(wait_sec)
            f.seek(where)
        else:
            print line, # already has newline

0
import itertools
fname = 'log.txt'
offset = 5
n = 10
with open(fname) as f:
    n_last_lines = list(reversed([x for x in itertools.islice(f, None)][-(offset+1):-(offset+n+1):-1]))

0
abc = "2018-06-16 04:45:18.68"
filename = "abc.txt"
with open(filename) as myFile:
    for num, line in enumerate(myFile, 1):
        if abc in line:
            lastline = num
print "last occurance of work at file is in "+str(lastline) 

0

Оновлення відповіді, надане А.Коаді

Працює з python 3 .

При цьому використовується Експонентний пошук і буферизується лише Nрядки ззаду і дуже ефективно.

import time
import os
import sys

def tail(f, n):
    assert n >= 0
    pos, lines = n+1, []

    # set file pointer to end

    f.seek(0, os.SEEK_END)

    isFileSmall = False

    while len(lines) <= n:
        try:
            f.seek(f.tell() - pos, os.SEEK_SET)
        except ValueError as e:
            # lines greater than file seeking size
            # seek to start
            f.seek(0,os.SEEK_SET)
            isFileSmall = True
        except IOError:
            print("Some problem reading/seeking the file")
            sys.exit(-1)
        finally:
            lines = f.readlines()
            if isFileSmall:
                break

        pos *= 2

    print(lines)

    return lines[-n:]




with open("stream_logs.txt") as f:
    while(True):
        time.sleep(0.5)
        print(tail(f,2))

-1

По-друге, це, мабуть, так само швидко, як що-небудь тут.

def tail( f, window=20 ):
    lines= ['']*window
    count= 0
    for l in f:
        lines[count%window]= l
        count += 1
    print lines[count%window:], lines[:count%window]

Це набагато простіше. І це, здається, зірве в хорошому темпі.


Тому що майже все тут не працює з файлами журналів, що мають більше 30 Мб або близько того, не завантажуючи в пам'ять однаковий обсяг пам’яті;) Ваша перша версія набагато краща, але для тестових файлів тут вона працює трохи гірше, ніж у мене і це не працює з різними символами нового рядка.
Армін Ронахер,

3
Я був неправий. Версія 1 займала 0,00248908996582 за 10 хвостів через словник. Версія 2 займала 1,22963051796 за 10 хвостів через словник. Я майже проголосував.
С.Лотт

"не працює з різними символами нового рядка." Замініть кількість даних ('\ n') на len (data.splitlines ()), якщо це має значення.
С.Лотт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.