Nhận n dòng cuối cùng của tệp bằng Python, tương tự như đuôi


181

Tôi đang viết trình xem tệp nhật ký cho một ứng dụng web và tôi muốn phân trang thông qua các dòng của tệp nhật ký. Các mục trong tệp là dòng dựa trên mục mới nhất ở phía dưới.

Vì vậy, tôi cần một tail()phương pháp có thể đọc ncác dòng từ dưới lên và hỗ trợ bù. Những gì tôi nghĩ ra trông như thế này:

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

Đây có phải là một cách tiếp cận hợp lý? Cách được đề xuất để đuôi các tệp nhật ký với offset là gì?


Trên hệ thống của tôi (linux SLES 10), việc tìm kiếm liên quan đến điểm cuối sẽ làm tăng IOError "không thể tìm kiếm kết quả tương đối khác". Tôi thích giải pháp này nhưng đã sửa đổi nó để có được độ dài tệp ( seek(0,2)sau đó tell()) và sử dụng giá trị đó để tìm kiếm liên quan đến đầu.
Anne

2
Xin chúc mừng - câu hỏi này đã được đưa vào mã nguồn Kippo
Miles

Các thông số của openlệnh được sử dụng để tạo ra các fđối tượng tập tin nên được chỉ định, vì tùy thuộc nếu f=open(..., 'rb')hay f=open(..., 'rt')những fphải được xử lý khác nhau
Igor Fobia

Câu trả lời:


123

Điều này có thể nhanh hơn của bạn. Làm cho không có giả định về chiều dài dòng. Quay lại từng khối một tệp cho đến khi tìm thấy đúng số ký tự '\ 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:])

Tôi không thích những giả định khó hiểu về độ dài của dòng khi - như một vấn đề thực tế - bạn không bao giờ có thể biết những thứ như vậy.

Nói chung, điều này sẽ xác định vị trí 20 dòng cuối cùng trên đường đầu tiên hoặc thứ hai đi qua vòng lặp. Nếu điều 74 ký tự của bạn thực sự chính xác, bạn tạo kích thước khối 2048 và bạn sẽ theo dõi 20 dòng gần như ngay lập tức.

Ngoài ra, tôi không đốt cháy nhiều calo trong não khi cố gắng sắp xếp sự liên kết với các khối hệ điều hành vật lý. Sử dụng các gói I / O cấp cao này, tôi nghi ngờ bạn sẽ thấy bất kỳ hậu quả hiệu suất nào của việc cố gắng căn chỉnh trên ranh giới khối hệ điều hành. Nếu bạn sử dụng I / O cấp thấp hơn, thì bạn có thể thấy tăng tốc.


CẬP NHẬT

đối với Python 3.2 trở lên, hãy thực hiện theo quy trình trên byte như trong tệp văn bản (những tệp được mở không có "b" trong chuỗi chế độ), chỉ tìm kiếm liên quan đến phần đầu của tệp (ngoại trừ việc tìm kiếm đến cuối tệp với tìm kiếm (0, 2)).:

ví dụ: 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
Điều này không thành công trên các logfiles nhỏ - IOError: đối số không hợp lệ - f.seek (khối * 1024, 2)
ohnoes

1
Cách tiếp cận rất tốt đẹp thực sự. Tôi đã sử dụng một phiên bản sửa đổi một chút của mã ở trên và đưa ra công thức này: code.activestate.com/recipes/577968-log-watcher-tail-f-log
Giampaolo Rodolà

6
Không còn hoạt động trong python 3.2. Tôi nhận được io.UnsupportedOperation: can't do nonzero end-relative seeksTôi có thể thay đổi bù thành 0, nhưng điều đó đánh bại mục đích của hàm.
Ngụy biện hợp lý

4
@DavidEnglund Lý do là đây . Tóm lại: không được phép tìm kiếm liên quan đến phần cuối của tệp trong chế độ văn bản, có lẽ vì nội dung tệp phải được giải mã và nói chung, tìm kiếm một vị trí tùy ý trong chuỗi byte được mã hóa có thể có kết quả không xác định khi bạn cố gắng giải mã Unicode bắt đầu từ vị trí đó. Đề xuất được cung cấp tại liên kết là thử mở tệp ở chế độ nhị phân và tự mình giải mã, bắt ngoại lệ DecodeError.
tối đa

6
KHÔNG SỬ DỤNG MÃ NÀY. Nó làm hỏng các dòng trong một số trường hợp viền trong python 2.7. Câu trả lời từ @aperscrane dưới đây khắc phục nó.
xApple

88

Giả sử một hệ thống giống như unix trên Python 2 bạn có thể làm:

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]

Đối với python 3 bạn có thể làm:

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
Nên độc lập nền tảng. Ngoài ra, nếu bạn đọc câu hỏi, bạn sẽ thấy f là một tệp giống như đối tượng.
Armin Ronacher

40
câu hỏi không nói sự phụ thuộc nền tảng là không thể chấp nhận được. Tôi không biết lý do tại sao điều này xứng đáng với hai downvote khi nó cung cấp một cách rất không hợp nhất (có thể là những gì bạn đang tìm kiếm ... chắc chắn là cho tôi) cách làm chính xác những gì câu hỏi yêu cầu.
Shabbycoat

3
Cảm ơn, tôi đã nghĩ rằng tôi phải giải quyết vấn đề này bằng Python thuần túy nhưng không có lý do gì để không sử dụng các tiện ích UNIX khi chúng có sẵn, vì vậy tôi đã sử dụng nó. FWIW trong Python hiện đại, sub process.checkDefput có khả năng thích hợp hơn os.popen2; nó đơn giản hóa mọi thứ một chút vì nó chỉ trả về đầu ra dưới dạng một chuỗi và tăng lên một mã thoát khác không.
mrooney

3
Mặc dù điều này phụ thuộc vào nền tảng, nhưng đây là một cách rất hiệu quả để thực hiện những gì đã được yêu cầu, cũng như là một cách thực hiện cực kỳ nhanh chóng (Bạn không phải tải toàn bộ tệp vào bộ nhớ). @Shabbycoat
earthmeLon

6
Bạn có thể muốn tính toán trước phần bù như: offset_total = str(n+offset)và thay thế dòng này stdin,stdout = os.popen2("tail -n "+offset_total+" "+f)để tránhTypeErrors (cannot concatenate int+str)
ThêmColor 8/10/2016

32

Đây là câu trả lời của tôi. Con trăn nguyên chất. Sử dụng timeit có vẻ khá nhanh. Đuôi 100 dòng của tệp nhật ký có 100.000 dòng:

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

Đây là mã:

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
Giải pháp tao nhã! Là if len(lines_found) > lines:thực sự cần thiết? Điều loopkiện này có bắt được không?
Maximilian Peters

Một câu hỏi cho sự hiểu biết của tôi: được os.SEEK_ENDsử dụng đơn giản cho rõ ràng? Theo như tôi đã tìm thấy, giá trị của nó là hằng số (= 2). Tôi đã tự hỏi về việc để nó ra ngoài để có thể rời khỏi import os. Cảm ơn giải pháp tuyệt vời!
n1k31t4

2
@MaximilianPeter có. Nó không cần thiết. Tôi nhận xét nó ra.
glenbot

@DexterMorgan bạn có thể thay thế os.SEEK_ENDbằng số nguyên tương đương. Nó chủ yếu ở đó để dễ đọc.
glenbot

1
Tôi nâng cấp, nhưng có một nit nhỏ. Sau khi tìm kiếm, dòng đầu tiên có thể không đầy đủ, vì vậy để có được N _complete_lines tôi đã thay đổi while len(lines_found) < linesthành while len(lines_found) <= linestrong bản sao của mình. Cảm ơn!
Graham Klyne

30

Nếu đọc toàn bộ tập tin là chấp nhận được thì sử dụng một deque.

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

Trước 2.6, deques không có tùy chọn maxlen, nhưng nó đủ dễ thực hiện.

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

Nếu đó là một yêu cầu để đọc tệp từ cuối, sau đó sử dụng một tìm kiếm phi nước đại (còn gọi là hàm mũ).

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

Tại sao chức năng dưới cùng đó hoạt động? pos *= 2Có vẻ hoàn toàn độc đoán. Ý nghĩa của nó là gì?
2mac

1
@ 2mac Tìm kiếm theo cấp số nhân . Nó đọc từ cuối tập tin lặp đi lặp lại, nhân đôi số lượng đọc mỗi lần, cho đến khi tìm thấy đủ dòng.
A. Coady

Tôi nghĩ rằng giải pháp đọc từ cuối sẽ không hỗ trợ các tệp được mã hóa bằng UTF-8, vì độ dài ký tự là biến đổi và bạn có thể (có thể sẽ) hạ cánh ở một số bù trừ không thể hiểu chính xác.
Mike

Thật không may, giải pháp tìm kiếm phi mã của bạn không hoạt động đối với python 3. Vì f.seek () không có bù trừ âm. Tôi đã cập nhật mã của bạn làm cho nó hoạt động cho liên kết
itjwala

25

Câu trả lời của S.Lott ở trên gần như có tác dụng với tôi nhưng cuối cùng lại cho tôi một phần dòng. Nó chỉ ra rằng nó làm hỏng dữ liệu trên các ranh giới khối vì dữ liệu giữ các khối đọc theo thứ tự đảo ngược. Khi '' .join (dữ liệu) được gọi, các khối theo thứ tự sai. Điều này khắc phục điều đó.

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
Chèn vào đầu danh sách là một ý tưởng tồi. Tại sao không sử dụng cấu trúc deque?
Sergey11g

1
Đáng buồn là Python 3 không tương thích ... cố gắng tìm hiểu tại sao.
Sherlock70

20

Mã tôi đã kết thúc bằng cách sử dụng. Tôi nghĩ rằng đây là tốt nhất cho đến nay:

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
không trả lời chính xác câu hỏi.
sheki

13

Giải pháp đơn giản và nhanh chóng với 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
Đây có lẽ là câu trả lời nhanh nhất khi đầu vào có thể rất lớn (hoặc nó sẽ là, nếu nó sử dụng .rfindphương pháp để quét ngược dòng mới, thay vì thực hiện byte ở mức kiểm tra thời gian ở cấp Python; trong CPython, thay thế mã cấp Python bằng C cuộc gọi tích hợp thường thắng rất nhiều). Đối với đầu vào nhỏ hơn, dequevới a maxlenđơn giản hơn và có thể nhanh tương tự.
ShadowRanger

4

Một phiên bản tương thích python3 thậm chí còn sạch hơn không chèn nhưng nối thêm & đảo ngược:

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

sử dụng nó như thế này:

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

Không quá tồi tàn - nhưng nói chung tôi sẽ khuyên không nên thêm câu trả lời cho câu hỏi 10 năm tuổi với nhiều câu trả lời. Nhưng hãy giúp tôi: có gì đặc biệt với Python 3 trong mã của bạn?
usr2564301

Những câu trả lời khác không chính xác làm việc diễn ra tốt đẹp :-) py3: xem stackoverflow.com/questions/136168/...
Hauke Rehfeld

3

Cập nhật giải pháp @aperscrane lên python3. Mở tệp bằng open(filename, 'rb')và:

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

Đăng một câu trả lời theo lệnh của những người bình luận về câu trả lời của tôi cho một câu hỏi tương tự trong đó kỹ thuật tương tự đã được sử dụng để làm biến đổi dòng cuối cùng của một tệp, không chỉ nhận được nó.

Đối với một tập tin có kích thước đáng kể, mmaplà cách tốt nhất để làm điều này. Để cải thiện mmapcâu trả lời hiện có , phiên bản này có thể di động giữa Windows và Linux và sẽ chạy nhanh hơn (mặc dù nó sẽ không hoạt động nếu không có một số sửa đổi trên Python 32 bit với các tệp trong phạm vi GB, hãy xem câu trả lời khác để biết gợi ý về cách xử lý này và để sửa đổi để hoạt động trên 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)

Điều này giả định số lượng dòng đuôi là đủ nhỏ để bạn có thể đọc tất cả chúng vào bộ nhớ một cách an toàn cùng một lúc; bạn cũng có thể biến chức năng này thành một trình tạo và đọc thủ công một dòng tại một thời điểm bằng cách thay thế dòng cuối cùng bằng:

        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)

Cuối cùng, điều này đọc ở chế độ nhị phân (cần thiết để sử dụng mmap) để nó đưa ra strcác dòng (Py2) và bytesdòng (Py3); nếu bạn muốn unicode(Py2) hoặc str(Py3), phương pháp lặp có thể được điều chỉnh để giải mã cho bạn và / hoặc sửa các dòng mới:

        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

Lưu ý: Tôi đã gõ tất cả lên trên một máy mà tôi thiếu quyền truy cập vào Python để kiểm tra. Xin vui lòng cho tôi biết nếu tôi đánh máy bất cứ điều gì; điều này đủ tương tự với câu trả lời khác của tôi mà tôi nghĩ rằng nó nên hoạt động, nhưng các điều chỉnh (ví dụ xử lý một offset) có thể dẫn đến các lỗi tinh vi. Xin vui lòng cho tôi biết trong các ý kiến ​​nếu có bất kỳ sai lầm.


3

Tôi thấy Popen ở trên là giải pháp tốt nhất. Nó nhanh và bẩn và nó hoạt động với python 2.6 trên máy Unix tôi đã sử dụng như sau

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 sẽ có chứa n dòng cuối của mã. để lặp qua dòng soutput theo dòng:

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

2

dựa trên câu trả lời được bình chọn hàng đầu của S.Lott (ngày 25 tháng 9 năm 08 lúc 21:43), nhưng đã được sửa cho các tệp nhỏ.

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

Hy vọng điều này là hữu ích.


2

Có một số triển khai đuôi trên pypi mà bạn có thể cài đặt bằng pip:

  • mtFileUtil
  • đa nhiệm
  • đăng nhập
  • ...

Tùy thuộc vào tình huống của bạn, có thể có những lợi thế khi sử dụng một trong những công cụ hiện có này.


Bạn có biết bất kỳ mô-đun nào hoạt động trên Windows? Tôi đã thử tailhead, tailernhưng họ đã không làm việc. Cũng đã thử mtFileUtil. Đó là lỗi ban đầu do các printcâu lệnh không có dấu ngoặc đơn (Tôi đang dùng Python 3.6). Tôi đã thêm những thông báo vào reverse.pyvà các thông báo lỗi đã biến mất nhưng khi tập lệnh của tôi gọi mô-đun ( mtFileUtil.tail(open(logfile_path), 5)), nó không in bất cứ thứ gì.
Technext

2

Đơn giản :

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

Đây là một thực hiện hoàn toàn xấu. Xem xét xử lý các tệp lớn và trong đó n cũng hoạt động quá lớn, quá tốn kém
Nivesh Krishna

1

Để đạt hiệu quả với các tệp rất lớn (phổ biến trong các tình huống logfile mà bạn có thể muốn sử dụng đuôi), bạn thường muốn tránh đọc toàn bộ tệp (ngay cả khi bạn làm điều đó mà không đọc toàn bộ tệp vào bộ nhớ cùng một lúc) cần bằng cách nào đó tìm ra phần bù trong dòng hơn là ký tự. Một khả năng là đọc ngược với tìm kiếm () char bằng char, nhưng điều này rất chậm. Thay vào đó, tốt hơn để xử lý trong các khối lớn hơn.

Tôi có một chức năng tiện ích tôi đã viết cách đây một thời gian để đọc các tập tin ngược có thể được sử dụng ở đây.

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

[Chỉnh sửa] Đã thêm phiên bản cụ thể hơn (tránh phải đảo ngược hai lần)


Một thử nghiệm nhanh cho thấy điều này thực hiện kém hơn nhiều so với phiên bản của tôi từ phía trên. Có lẽ là do bộ đệm của bạn.
Armin Ronacher

Tôi nghi ngờ đó là vì tôi đang thực hiện nhiều lần tìm kiếm ngược, vì vậy sẽ không sử dụng tốt bộ đệm đọc. Tuy nhiên, tôi nghĩ rằng nó có thể làm tốt hơn khi dự đoán của bạn ở độ dài dòng không chính xác (ví dụ: các dòng rất lớn), vì nó tránh phải đọc lại dữ liệu trong trường hợp này.
Brian

1

bạn có thể đi đến cuối tệp của mình với f.seek (0, 2) và sau đó đọc từng dòng một với sự thay thế sau cho 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

Dựa trên câu trả lời của Eyiru (10 tháng 10, 10 lúc 21:28): lớp này thêm phương thức head () và tail () vào đối tượng tệp.

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

Sử dụng:

f = File('path/to/file', 'r')
f.head(3)
f.tail(3)

1

Một số giải pháp này có vấn đề nếu tệp không kết thúc bằng \ n hoặc để đảm bảo dòng đầu tiên hoàn chỉnh được đọc.

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

Đây là một cách thực hiện khá đơn giản:

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

Ví dụ tuyệt vời! Bạn có thể vui lòng giải thích việc sử dụng thử trước khi f.seek? Tại sao không trước with open? Ngoài ra, tại sao trong exceptbạn làm một f.readlines()??

Thành thật mà nói, có lẽ nên thử trước .. Tôi không nhớ có lý do gì để không mở () ngoài hệ thống Linux tiêu chuẩn lành mạnh, / etc / passwd luôn luôn có thể đọc được. cố gắng, sau đó với là thứ tự phổ biến hơn.
GL2014

1

Có một mô-đun rất hữu ích có thể làm điều này:

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

Giải pháp khác

nếu tập tin txt của bạn trông như thế này: chuột rắn mèo thằn lằn chó sói

bạn có thể đảo ngược tệp này bằng cách sử dụng lập chỉ mục mảng trong 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)

kết quả: chó sói thằn lằn mèo


1

Cách đơn giản nhất là sử dụng deque:

from collections import deque

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

0

Tôi đã phải đọc một giá trị cụ thể từ dòng cuối cùng của một tập tin, và tình cờ thấy chủ đề này. Thay vì phát minh lại bánh xe trong Python, tôi đã kết thúc với một tập lệnh shell nhỏ, được lưu dưới dạng / usr / local / bin / get_last_netp:

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

Và trong chương trình Python:

from subprocess import check_output

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

0

Không phải là ví dụ đầu tiên sử dụng một deque, nhưng một ví dụ đơn giản hơn. Điều này là chung: nó hoạt động trên bất kỳ đối tượng lặp, không chỉ là một tập tin.

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

Cập nhật cho câu trả lời được đưa ra bởi A.Coady

Hoạt động với trăn 3 .

Điều này sử dụng Tìm kiếm theo cấp số nhân và sẽ chỉ đệm Ncác dòng từ phía sau và rất hiệu quả.

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

Về ý nghĩ thứ hai, điều này có lẽ cũng nhanh như mọi thứ ở đây.

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]

Nó đơn giản hơn nhiều. Và nó dường như tách ra với tốc độ tốt.


Bởi vì gần như mọi thứ ở đây không hoạt động với các tệp nhật ký có dung lượng lớn hơn 30 MB mà không tải cùng một bộ nhớ vào RAM;) Phiên bản đầu tiên của bạn tốt hơn rất nhiều, nhưng đối với các tệp thử nghiệm ở đây, nó hoạt động kém hơn tôi một chút và nó không hoạt động với các ký tự dòng mới khác nhau.
Armin Ronacher

3
Tôi đã sai. Phiên bản 1 lấy 0,00248908996582 cho 10 đuôi thông qua từ điển. Phiên bản 2 lấy 1.2963051796 cho 10 đuôi thông qua từ điển. Tôi gần như bỏ phiếu cho mình xuống.
S.Lott

"không hoạt động với các ký tự dòng mới khác nhau." Thay thế datacount ('\ n') bằng len (data.splitlines ()) nếu nó quan trọng.
S.Lott
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.