Эффективное обнаружение последней строки в текстовом файле

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

Каков наилучший способ получить только последнюю строку текстового файла с помощью python?

Ответ 2

Не прямой путь, но, вероятно, намного быстрее, чем простая реализация Python:

line = subprocess.check_output(['tail', '-1', filename])

Ответ 3

Используйте метод seek с отрицательным смещением и whence=os.SEEK_END, чтобы прочитать блок с конца файла. Найдите этот блок для последнего символа конца строки и возьмите все символы после него. Если нет конца строки, вернитесь назад и повторите процесс.

def last_line(in_file, block_size=1024, ignore_ending_newline=False):
    suffix = ""
    in_file.seek(0, os.SEEK_END)
    in_file_length = in_file.tell()
    seek_offset = 0

    while(-seek_offset < in_file_length):
        # Read from end.
        seek_offset -= block_size
        if -seek_offset > in_file_length:
            # Limit if we ran out of file (can't seek backward from start).
            block_size -= -seek_offset - in_file_length
            if block_size == 0:
                break
            seek_offset = -in_file_length
        in_file.seek(seek_offset, os.SEEK_END)
        buf = in_file.read(block_size)

        # Search for line end.
        if ignore_ending_newline and seek_offset == -block_size and buf[-1] == '\n':
            buf = buf[:-1]
        pos = buf.rfind('\n')
        if pos != -1:
            # Found line end.
            return buf[pos+1:] + suffix

        suffix = buf + suffix

    # One-line file.
    return suffix

Обратите внимание, что это не будет работать на вещах, которые не поддерживают seek, например, stdin или сокеты. В этих случаях вы застряли, читая все это (например, команда tail).

Ответ 4

Если вы знаете максимальную длину строки, вы можете сделать

def getLastLine(fname, maxLineLength=80):
    fp=file(fname, "rb")
    fp.seek(-maxLineLength-1, 2) # 2 means "from the end of the file"
    return fp.readlines()[-1]

Это работает на моем компьютере с Windows. Но я не знаю, что происходит на других платформах, если вы открываете текстовый файл в двоичном режиме. Бинарный режим необходим, если вы хотите использовать функцию seek().

Ответ 5

Ищите конец файла минус 100 байт или около того. Прочитайте и найдите новую строку. Если здесь нет новой строки, запросите еще 100 байт или около того. Намочите, промойте, повторите. В конце концов вы найдете новую строку. Последняя строка начинается сразу после этой строки.

Лучший случайный сценарий: вы читаете только 100 байт.

Ответ 6

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

myfile.seek(-max_line_length, os.SEEK_END)
line = myfile.readlines()[-1]

Ответ 7

with open('output.txt', 'r') as f:
    lines = f.read().splitlines()
    last_line = lines[-1]
    print last_line

Ответ 8

Не удалось загрузить файл в mmap, затем используйте mmap.rfind(string [, start [, end]]), чтобы найти второй последний символ EOL в файле? Поиск этой точки в файле должен указывать на последнюю строку, о которой я думаю.

Ответ 9

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

from os import SEEK_END

def get_last_line(file):
  CHUNK_SIZE = 1024 # Would be good to make this the chunk size of the filesystem

  last_line = ""

  while True:
    # We grab chunks from the end of the file towards the beginning until we 
    # get a new line
    file.seek(-len(last_line) - CHUNK_SIZE, SEEK_END)
    chunk = file.read(CHUNK_SIZE)

    if not chunk:
      # The whole file is one big line
      return last_line

    if not last_line and chunk.endswith('\n'):
      # Ignore the trailing newline at the end of the file (but include it 
      # in the output).
      last_line = '\n'
      chunk = chunk[:-1]

    nl_pos = chunk.rfind('\n')
    # What being searched for will have to be modified if you are searching
    # files with non-unix line endings.

    last_line = chunk[nl_pos + 1:] + last_line

    if nl_pos == -1:
      # The whole chunk is part of the last line.
      continue

    return last_line

Ответ 10

Здесь немного другое решение. Вместо многострочного, я сосредоточился только на последней строке, и вместо постоянного размера блока у меня есть динамический (удвоение) размер блока. См. Комментарии для получения дополнительной информации.

# Get last line of a text file using seek method.  Works with non-constant block size.  
# IDK if that speed things up, but it good enough for us, 
# especially with constant line lengths in the file (provided by len_guess), 
# in which case the block size doubling is not performed much if at all.  Currently,
# we're using this on a textfile format with constant line lengths.
# Requires that the file is opened up in binary mode.  No nonzero end-rel seeks in text mode.
REL_FILE_END = 2
def lastTextFileLine(file, len_guess=1):
    file.seek(-1, REL_FILE_END)      # 1 => go back to position 0;  -1 => 1 char back from end of file
    text = file.read(1)
    tot_sz = 1              # store total size so we know where to seek to next rel file end
    if text != b'\n':        # if newline is the last character, we want the text right before it
        file.seek(0, REL_FILE_END)    # else, consider the text all the way at the end (after last newline)
        tot_sz = 0
    blocks = []           # For storing succesive search blocks, so that we don't end up searching in the already searched
    j = file.tell()          # j = end pos
    not_done = True
    block_sz = len_guess
    while not_done:
        if j < block_sz:   # in case our block doubling takes us past the start of the file (here j also = length of file remainder)
            block_sz = j
            not_done = False
        tot_sz += block_sz
        file.seek(-tot_sz, REL_FILE_END)         # Yes, seek() works with negative numbers for seeking backward from file end
        text = file.read(block_sz)
        i = text.rfind(b'\n')
        if i != -1:
            text = text[i+1:].join(reversed(blocks))
            return str(text)
        else:
            blocks.append(text)
            block_sz <<= 1    # double block size (converge with open ended binary search-like strategy)
            j = j - block_sz      # if this doesn't work, try using tmp j1 = file.tell() above
    return str(b''.join(reversed(blocks)))      # if newline was never found, return everything read

В идеале вы бы обернули это в класс LastTextFileLine и отслеживали скользящее среднее длины строк. Это может дать вам хороший len_guess.

Ответ 11

lines = file.readlines()
fileHandle.close()
last_line = lines[-1]

Ответ 12

Вам не нужно перебирать все строки. Можете ли вы просто прочитать файлы в списке и индексировать последнюю строку?

Я нашел этот пример:

http://www.daniweb.com/software-development/python/threads/121557

Ответ 13

!/USR/бен/питон

count = 0

f = open ('last_line1', 'r')

для строки в f.readlines():

line = line.strip()

count = count + 1

print line

количество распечаток

f.close()

count1 = 0

h = open ('last_line1', 'r')

для строки в h.readlines():

line = line.strip()

count1 = count1 + 1

if count1 == count:

   print line         #------------------------- this is the last line

h.close()