Получить последние 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

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

В моей системе (linux SLES 10) поиск относительно конца вызывает ошибку IOError «не может выполнить поиск с ненулевым относительным концом». Мне нравится это решение, но я изменил его, чтобы получить длину файла (seek(0,2), затем tell()), и использовать это значение для поиска относительно начала.

Anne 07.02.2012 21:19

Поздравляю - этот вопрос вошел в исходный код Kippo

Miles 28.02.2014 14:15

Параметры команды open, используемой для создания файлового объекта f, должны быть указаны, потому что в зависимости от того, f=open(..., 'rb') или f=open(..., 'rt'), f должен обрабатываться по-разному.

Igor Fobia 05.02.2020 12:37
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
188
3
162 093
33
Перейти к ответу Данный вопрос помечен как решенный

Ответы 33

Предполагается, что на Python 2 есть unix-подобная система, вы можете:

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]

Должен быть независимой от платформы. Кроме того, если вы прочитаете вопрос, вы увидите, что f - это объект, подобный файлу.

Armin Ronacher 26.09.2008 01:57

вопрос не говорит о том, что зависимость от платформы недопустима. Я не понимаю, почему это заслуживает двух отрицательных голосов, когда он обеспечивает очень unixy (может быть, то, что вы ищете ... определенно было для меня) способ делать именно то, что задает вопрос.

Shabbyrobe 03.06.2009 08:27

Спасибо, я думал, что мне нужно решить эту проблему на чистом Python, но нет никаких причин не использовать утилиты UNIX, когда они есть под рукой, поэтому я пошел с этим. FWIW в современном Python, subprocess.check_output, вероятно, предпочтительнее os.popen2; он немного упрощает работу, поскольку он просто возвращает вывод в виде строки и вызывает ненулевой код выхода.

mrooney 30.10.2013 05:35

Хотя это зависит от платформы, это эффективный способ очень делать то, что было запрошено, а также чрезвычайно быстрый способ сделать это (вам не нужно загружать весь файл в память). @Shabbyrobe

earthmeLon 22.11.2014 01:53

@Mark обновление может быть приятным, поскольку popen2 устарел с pyton2.6

ezdazuzena 08.03.2016 15:00

Вы можете предварительно рассчитать смещение, например: offset_total = str(n+offset), и заменить эту строку stdin,stdout = os.popen2("tail -n "+offset_total+" "+f), чтобы избежать TypeErrors (cannot concatenate int+str)

AddingColor 08.10.2016 18:47

Рассмотрим tail('file.txt; rm -rf /', 10) (НЕ НАЗЫВАЙТЕ ЭТО)

Francisco C 20.10.2016 23:35

@FranciscoCouzo, это не проблема. Я не вижу никаких пользовательских вводов, проблем с внедрением типа sql. Любой, кто мог бы выполнить сценарий python, уже имел доступ к командной строке, где он мог запускать эту команду напрямую.

Mark 20.10.2016 23:38

@Mark конечно, но любой, кто читает ответ, может запускать его при вводе пользователем.

Francisco C 20.10.2016 23:43

python3: ДО ​​-> proc = subprocess.Popen(['tail', '-n', n + offset, f], stdout=subprocess.PIPE) ПОСЛЕ -> proc = subprocess.Popen(['tail', '-n', '\"%s\"' % (n + offset), file_path], stdout=subprocess.PIPE) для предотвращения ошибки

gies0r 11.07.2019 23:10

Я получаю: ожидаемый объект str, bytes или os.PathLike, а не int с proc = subprocess.Popen(['tail', '-n', n + offset, f], stdout=subprocess.PIPE)

Timo 02.01.2021 13:55

Можете показать, как вы это называете. По-прежнему получаю ошибку от 2-го января. Я называю это весело ('file.txt', 10)

Timo 28.01.2021 22:14

Для повышения эффективности работы с очень большими файлами (что часто встречается в ситуациях с файлами журнала, когда вы можете захотеть использовать 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

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

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

Armin Ronacher 26.09.2008 02:00

Я подозреваю, что это потому, что я выполняю несколько поисков в обратном направлении, поэтому я не так хорошо использую буфер опережающего чтения. Однако я думаю, что это может быть лучше, когда ваше предположение о длине строки неточно (например, очень большие строки), поскольку в этом случае не нужно повторно читать данные.

Brian 26.09.2008 02:23

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

Хороший. По крайней мере, один плакат, который прочитал вопрос и код там :)

Armin Ronacher 26.09.2008 01:55

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

Jeff Hellman 16.07.2009 09:26

Это не удается для небольших файлов журнала - IOError: недопустимый аргумент - f.seek (block * 1024, 2)

ohnoes 04.12.2009 14:19

Некоторое время назад я отредактировал это решение, оно еще не прошло рецензирование (снова). Перед соединением строки data необходимо поменять местами, поскольку файл читается в обратном порядке.

Pykler 04.10.2011 22:21

Угловой случай: последняя строка больше, чем BUFSIZ и window == 1. Может, linesFound должен быть чем-то вроде data[-1].count('\n') - 1?

sje397 13.10.2011 05:52

Действительно, очень хороший подход. Я использовал немного измененную версию кода выше и придумал такой рецепт: code.activestate.com/recipes/577968-log-watcher-tail-f-log

Giampaolo Rodolà 29.11.2011 23:32

Больше не работает в python 3.2. Я получаю io.UnsupportedOperation: can't do nonzero end-relative seeks. Я могу изменить смещение на 0, но это противоречит цели функции.

Logical Fallacy 02.05.2012 01:27

@DavidEnglund Причина: здесь. Вкратце: поиск относительно конца файла не разрешен в текстовом режиме, предположительно потому, что содержимое файла должно быть декодировано, и, как правило, поиск произвольной позиции в последовательности закодированных байтов может иметь неопределенные результаты, когда вы попытаться декодировать в Юникод, начиная с этой позиции. По ссылке предлагается попробовать открыть файл в двоичном режиме и выполнить декодирование самостоятельно, перехватив исключения DecodeError.

max 10.09.2012 23:23

НЕ ИСПОЛЬЗУЙТЕ ЭТОТ КОД. Он повреждает строки в некоторых пограничных случаях в python 2.7. Ответ от @papercrane ниже исправляет это.

xApple 29.04.2013 14:19

Я получаю invalid literal for int() with base 12: "07b925503'," для некоторых моих линий. Ответ @ papercrane работает хорошо.

AliBZ 23.01.2014 22:10

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

Mark Amery 24.08.2014 00:07

Просто подумал, что оставлю небольшую заметку с информацией о тестировании. Кажется, это даже быстрее, чем стрелять в хвост - спасибо! Подробнее см .: gitweb.torproject.org/stem.git/commit/?id=8736a7e

Damian 24.03.2015 19:39

Примечание: вам не нужно «сжигать много мозговых калорий, пытаясь добиться согласованности с физическими блоками ОС»; получить идеальный размер блока во многих операционных системах несложно, и Python предоставляет разумные значения по умолчанию для систем (например, Windows), которые этого не делают. Просто измените определение BLOCK_SIZE на BLOCK_SIZE = getattr(os.fstat(f.fileno()), 'st_blksize', io.DEFAULT_BUFFER_SIZE), и он будет использовать предпочтительный размер блока ввода-вывода, о котором сообщается самим файлом, или резервное значение по умолчанию Python (которое в настоящее время составляет 8 КБ, но все еще достаточно мало, чтобы вы не ожидали серьезных замедлений, если строки короткие).

ShadowRanger 03.10.2018 20:40

Если подумать, это, вероятно, так же быстро, как и все здесь.

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 МБ или около того без загрузки того же объема памяти в ОЗУ;) Ваша первая версия намного лучше, но для тестовых файлов здесь она работает немного хуже, чем моя и он не работает с разными символами новой строки.

Armin Ronacher 26.09.2008 02:06

Я был неправ. Версия 1 заняла 0,00248908996582 для 10 хвостов по словарю. Версия 2 заняла 1.2963051796 за 10 хвостов по словарю. Я бы почти проголосовал против.

S.Lott 26.09.2008 02:06

"не работает с разными символами новой строки". Замените datacount ('\ n') на len (data.splitlines ()), если это важно.

S.Lott 26.09.2008 02:15

Если чтение всего файла приемлемо, используйте двухстороннюю очередь.

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:]

Почему работает эта нижняя функция? pos *= 2 кажется совершенно произвольным. В чем его значение?

2mac 29.12.2014 22:06

@ 2mac Экспоненциальный поиск. Он читает с конца файла итеративно, удваивая количество прочитанных каждый раз, пока не будет найдено достаточное количество строк.

A. Coady 17.03.2015 02:08

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

Mike 17.02.2019 06:53

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

itsjwala 30.07.2019 20:38

Вот из документации, что делает двухсторонняя очередь: как только двухсторонняя очередь ограниченной длины заполнена, при добавлении новых элементов соответствующее количество элементов отбрасывается с противоположной стороны. Если n = 1, он читает последнюю (или единственную строку) из файла. Почему вы предлагаете хвостовой метод, когда deque делает то же самое?

Timo 02.01.2021 13:04

Однострочник для последней строки с python 3: with open('example', 'r') as f:d = deque(f, maxlen=1)

Timo 02.01.2021 13:23

@Timo: Функция tail, как указано в OP, предназначена для того, чтобы вы необходимость читали файл с конца. Если вам нужно прочитать последнюю строку файла размером 50 ГБ, конечно, deque(f, maxlen=1) в конечном итоге будет работать. На вращающемся дисководе со скоростью 100 МБ / с без конкуренции и без фрагментации это займет всего восемь с половиной минут (он не может волшебным образом пропустить все другие строки, он должен их прочитать и отбросить. как идет). Или вы используете поиск в обратном направлении и делаете это за <вставьте тривиально короткое время здесь>.

ShadowRanger 06.01.2021 23:17
Ответ принят как подходящий

Код, который я в итоге использовал. Я думаю, что это пока лучший вариант:

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

не совсем отвечает на вопрос.

sheki 13.02.2012 17:43

на основе наиболее популярных ответов С.Лотта (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

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

вы можете перейти в конец файла с помощью 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

На основе ответа Eyecue (10 июня 2010 г., 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)

У некоторых из этих решений есть проблемы, если файл не заканчивается на \ 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

Простое и быстрое решение с 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()

Это, вероятно, самый быстрый ответ, когда ввод может быть огромным (или это было бы, если бы он использовал метод .rfind для сканирования в обратном направлении на предмет новой строки, а не для выполнения побайтных проверок на уровне Python; в CPython, заменяя код уровня Python со встроенными вызовами C обычно выигрывает много). Для небольших входов deque с maxlen проще и, вероятно, так же быстро.

ShadowRanger 19.11.2015 21:41

Ответ S.Lott выше почти работает для меня, но в итоге дает мне частичные строки. Оказывается, это повреждает данные на границах блоков, потому что данные содержат считанные блоки в обратном порядке. Когда вызывается .join (data), блоки расположены в неправильном порядке. Это исправляет это.

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:]

Вставлять в начало списка - плохая идея. Почему бы не использовать структуру deque?

Sergey11g 29.09.2017 11:26

К сожалению, не совместим с Python 3 ... пытаюсь понять, почему.

Sherlock70 06.02.2020 18:15

Я нашел, что Popen выше - лучшее решение. Это быстро, грязно и работает Для 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

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

  • mtFileUtil
  • многоцелевой
  • log4tailer
  • ...

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

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

Technext 19.09.2018 14:58

Вот мой ответ. Чистый питон. Использование timeit кажется довольно быстрым. Отслеживание 100 строк файла журнала, содержащего 100000 строк:

>>> 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:]

Шикарное решение! Действительно ли нужен if len(lines_found) > lines:? Разве условие loop не улавливает это?

Maximilian Peters 23.07.2016 11:45

Вопрос для понимания: используется ли os.SEEK_END просто для наглядности? Насколько я понял, его значение постоянно (= 2). Мне было интересно, оставить ли это, чтобы можно было не учитывать import os. Спасибо за отличное решение!

n1k31t4 05.10.2017 13:51

@MaximilianPeters да. Это необязательно. Я это прокомментировал.

glenbot 06.10.2017 17:21

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

glenbot 06.10.2017 17:22

Я проголосовал за, но у меня есть небольшая проблема. После поиска чтение первой строки может быть неполным, поэтому, чтобы получить N _complete_lines, я изменил while len(lines_found) < lines на while len(lines_found) <= lines в своей копии. Спасибо!

Graham Klyne 29.08.2018 14:11

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

BlackJack 03.03.2021 16:02

Мне пришлось прочитать конкретное значение из последней строки файла и наткнулся на эту цепочку. Вместо того, чтобы изобретать колесо в Python, я получил крошечный сценарий оболочки, сохраненный как / usr / местные / bin / get_last_netp:

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

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

from subprocess import check_output

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

Не первый пример использования двухсторонней очереди, но более простой. Этот общий: он работает с любым повторяемым объектом, а не только с файлом.

#!/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)

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

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

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

Для файла значительного размера 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) могут привести к незначительным ошибкам. Пожалуйста, дайте мне знать в комментариях, если есть ошибки.

Хотя это не очень эффективно для больших файлов, этот код довольно прост:

  1. Он читает файловый объект f.
  2. Он разбивает строку, возвращаемую с использованием новой строки, \n.
  3. Он получает список последних индексов, используя отрицательный знак для обозначения последних индексов и : для получения подмассива.

    def tail(f,n):
        return "\n".join(f.read().split("\n")[-n:])
    

Человек, проголосовавший против моего ответа, не могли бы вы объяснить, почему?

WorkingRobot 11.06.2016 01:28

в самый первый момент, когда вы используете f.read() и не выполняете поиск в обработчике файлов, вы помещаете ВСЕ свой файл в память. Буферизация всего файла (без поиска) НЕПРАВИЛЬНА, поэтому ваши ответы на самом деле не добавляют ничего нового к проблеме, а просто еще один способ наполнить вашу память. Теперь попробуйте использовать свой код с файлом размером 10 ГБ, а затем посмотрите, что произойдет. Использование itertools - еще один способ попробовать, но оба стремиться и хвост сделают свое дело. Вам не нужно помещать все свои строки в память для их обработки, вы можете размещать их по частям. Надеюсь, ты понимаешь.

m3nda 27.12.2016 16:28

Эта функция не должна была быть завершенной функцией. Это был просто наихудший сценарий.

WorkingRobot 27.12.2016 17:23

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

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

Отличный пример! Не могли бы вы объяснить, как использовать try до f.seek? Почему не до with open? Кроме того, почему в except вы делаете f.readlines() ??

user6003691 07.07.2017 18:33

Честно говоря, попытка, вероятно, должна быть первой. Я не помню, чтобы у меня была причина не ловить open (), кроме как в здоровой стандартной системе Linux, / etc / passwd всегда должен быть читаемым. попробуйте, то с более распространенным порядком.

GL2014 13.07.2017 23:11

Обновите решение @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:]

Еще более чистая версия, совместимая с 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?

Jongware 04.01.2018 04:58

Другие ответы не совсем удались :-) py3: см. stackoverflow.com/questions/136168/…

Hauke Rehfeld 05.01.2018 00:38

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

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) 

Я нашел, наверное, самый простой способ найти первые или последние N строк файла

Последние N строк файла (например, N = 10)

file=open("xyz.txt",'r")
liner=file.readlines()
for ran in range((len(liner)-N),len(liner)):
    print liner[ran]

Первые N строк файла (например, N = 10)

file=open("xyz.txt",'r")
liner=file.readlines()
for ran in range(0,N+1):
    print liner[ran]

это так просто:

def tail(fname,nl):
with open(fname) as f:
    data=f.readlines() #readlines return a list
    print(''.join(data[-nl:]))

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

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)

Простой :

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

Это совершенно плохая реализация. Рассмотрите возможность обработки огромных файлов, а где n также огромное, слишком дорогостоящая операция.

Nivesh Krishna 25.05.2019 17:15

Обновление для ответа А.Коди

Работает с питон 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))

Другое решение

если ваш текстовый файл выглядит так: мышь змея кошка ящерица волк собака

вы можете отменить этот файл, просто используя индексирование массива в 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)

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

Самый простой способ - использовать deque:

from collections import deque

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

Это будет проходить через весь файл. Имейте это в виду, если вы работаете с большими файлами.

Austin 21.08.2020 20:32

Другие вопросы по теме