Dapatkan n baris terakhir file, mirip dengan tail

181

Saya sedang menulis penampil file log untuk aplikasi web dan untuk itu saya ingin memberi paginasi melalui baris file log. Item dalam file tersebut berdasarkan garis dengan item terbaru di bagian bawah.

Jadi saya memerlukan tail()metode yang dapat membaca nbaris dari bawah dan mendukung offset. Apa yang saya hasilkan terlihat seperti ini:

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

Apakah ini pendekatan yang masuk akal? Apa cara yang disarankan untuk mengekor file log dengan offset?

Armin Ronacher
sumber
Pada sistem saya (linux SLES 10), mencari relatif ke ujung menimbulkan IOError "tidak dapat melakukan pencarian relatif bukan-nol". Saya suka solusi ini tetapi telah memodifikasinya untuk mendapatkan panjang file ( seek(0,2)lalu tell()), dan menggunakan nilai itu untuk mencari relatif ke awal.
Anne
2
Selamat - pertanyaan ini membuatnya menjadi kode sumber Kippo
Miles
Parameter dari openperintah yang digunakan untuk menghasilkan ffile objek harus ditentukan, karena tergantung jika f=open(..., 'rb')atau f=open(..., 'rt')yang fharus diproses secara berbeda
Igor Fobia

Jawaban:

123

Ini mungkin lebih cepat dari milikmu. Tidak membuat asumsi tentang panjang garis. Mundur melalui file satu blok pada satu waktu sampai ditemukan jumlah karakter '\ n' yang tepat.

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

Saya tidak suka asumsi rumit tentang panjang garis ketika - sebagai masalah praktis - Anda tidak akan pernah tahu hal-hal seperti itu.

Secara umum, ini akan menemukan 20 baris terakhir pada lintasan pertama atau kedua melalui loop. Jika 74 karakter Anda benar-benar akurat, Anda membuat ukuran blok 2048 dan Anda akan segera mengekor 20 garis.

Juga, saya tidak membakar banyak kalori otak mencoba untuk menyelaraskan dengan blok OS fisik. Menggunakan paket I / O tingkat tinggi ini, saya ragu Anda akan melihat konsekuensi kinerja dari mencoba menyelaraskan pada batas blok OS. Jika Anda menggunakan I / O tingkat rendah, maka Anda mungkin melihat speedup.


MEMPERBARUI

untuk Python 3.2 dan ke atas, ikuti proses pada byte sebagai Dalam file teks (yang dibuka tanpa "b" dalam string mode), hanya mencari relatif terhadap awal file yang diizinkan (pengecualian sedang mencari hingga akhir file dengan seek (0, 2)) .:

misalnya: 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:])
S.Lott
sumber
13
Ini gagal pada file log kecil - IOError: argumen tidak valid - f.seek (blok * 1024, 2)
ohnoes
1
Pendekatan yang sangat bagus. Saya menggunakan versi yang sedikit dimodifikasi dari kode di atas dan muncul dengan resep ini: code.activestate.com/recipes/577968-log-watcher-tail-f-log
Giampaolo Rodolà
6
Tidak lagi berfungsi di python 3.2. Saya mendapatkan io.UnsupportedOperation: can't do nonzero end-relative seekssaya dapat mengubah offset ke 0, tapi itu mengalahkan tujuan fungsi.
Logical Fallacy
4
@DavidEnglund Reason ada di sini . Singkatnya: mencari relatif ke akhir file tidak diperbolehkan dalam mode teks, mungkin karena konten file harus diterjemahkan, dan, secara umum, mencari posisi sewenang-wenang dalam urutan byte yang dikodekan dapat memiliki hasil yang tidak ditentukan ketika Anda mencoba untuk memecahkan kode ke Unicode mulai dari posisi itu. Saran yang ditawarkan di tautan adalah untuk mencoba membuka file dalam mode biner dan melakukan decoding sendiri, dengan menangkap pengecualian DecodeError.
maks
6
JANGAN GUNAKAN KODE INI. Ini merusak garis dalam beberapa kasus perbatasan di python 2.7. Jawaban dari @papercrane di bawah ini memperbaikinya.
xApple
88

Mengasumsikan sistem unix-like pada Python 2 yang dapat Anda lakukan:

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]

Untuk python 3, Anda dapat melakukannya:

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]
Menandai
sumber
5
Harus independen dari platform. Selain itu, jika Anda membaca pertanyaan Anda akan melihat bahwa f adalah objek seperti file.
Armin Ronacher
40
pertanyaannya tidak mengatakan ketergantungan platform tidak dapat diterima. Saya gagal untuk melihat mengapa ini layak dua downvotes ketika memberikan cara yang sangat unixy (mungkin apa yang Anda cari ... tentu bagi saya) melakukan apa yang diminta pertanyaan.
Shabbyrobe
3
Terima kasih, saya berpikir saya harus menyelesaikan ini dengan Python murni tetapi tidak ada alasan untuk tidak menggunakan utilitas UNIX ketika mereka sudah dekat, jadi saya pergi dengan ini. FWIW dalam Python modern, subprocess.check_output lebih disukai daripada os.popen2; itu menyederhanakan hal-hal sedikit karena hanya mengembalikan output sebagai string, dan memunculkan kode keluar non-nol.
mrooney
3
Meskipun ini tergantung pada platform, ini adalah cara yang sangat efisien untuk melakukan apa yang telah diminta, serta menjadi cara yang sangat cepat untuk melakukannya (Anda tidak perlu memuat seluruh file ke dalam memori). @Shabbyrobe
earthmeLon
6
Anda mungkin ingin menghitung ulang offset seperti: offset_total = str(n+offset)dan ganti baris ini stdin,stdout = os.popen2("tail -n "+offset_total+" "+f)untuk menghindariTypeErrors (cannot concatenate int+str)
AddingColor
32

Inilah jawaban saya. Python murni. Menggunakan timeit sepertinya cukup cepat. Tailing 100 baris file log yang memiliki 100.000 baris:

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

Ini kodenya:

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:]
Glenbot
sumber
3
Solusi elegan! Apakah ini if len(lines_found) > lines:benar - benar perlu? Bukankah loopkondisinya akan menangkapnya juga?
Maximilian Peters
Sebuah pertanyaan untuk pemahaman saya: os.SEEK_ENDdigunakan hanya untuk kejelasan? Sejauh yang saya temukan, nilainya konstan (= 2). Saya bertanya-tanya tentang meninggalkannya untuk dapat meninggalkan import os. Terima kasih atas solusi hebatnya!
n1k31t4
2
@ MaximilianPeters ya. Itu tidak perlu. Saya berkomentar.
glenbot
@DexterMorgan dapat Anda ganti os.SEEK_ENDdengan yang setara dengan integer. Itu terutama di sana untuk dibaca.
glenbot
1
Saya terbalik, tetapi memiliki nit kecil. Setelah pencarian, membaca baris pertama mungkin tidak lengkap, jadi untuk mendapatkan N _complete_lines saya mengubah while len(lines_found) < lineske while len(lines_found) <= linesdalam salinan saya. Terima kasih!
Graham Klyne
30

Jika membaca seluruh file dapat diterima maka gunakan deque.

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

Sebelum 2.6, deques tidak memiliki opsi maksimal, tetapi cukup mudah untuk diterapkan.

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

Jika itu adalah persyaratan untuk membaca file dari akhir, maka gunakan pencarian gallop (alias eksponensial).

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:]
A. Coady
sumber
Mengapa fungsi dasar itu berfungsi? pos *= 2tampaknya sepenuhnya arbitrer. Apa maknanya?
2mac
1
@ 2mac Pencarian Eksponensial . Bunyinya dari akhir file secara iteratif, menggandakan jumlah yang dibaca setiap kali, sampai cukup banyak baris ditemukan.
A. Coady
Saya pikir solusi untuk membaca dari akhir tidak akan mendukung file yang dikodekan dengan UTF-8, karena panjang karakternya variabel, dan Anda (kemungkinan akan) mendarat di beberapa offset aneh yang tidak dapat diartikan dengan benar.
Mike
sayangnya solusi pencarian berderap Anda tidak berfungsi untuk python 3. Karena f.seek () tidak mengambil offset negatif. Saya telah memperbarui kode Anda agar berfungsi untuk tautan
itsjwala
25

Jawaban S.Lott di atas hampir berhasil untuk saya tetapi akhirnya memberi saya sebagian garis. Ternyata itu merusak data pada batas blok karena data memegang blok baca dalam urutan terbalik. Ketika '.join (data) dipanggil, blok-bloknya berada dalam urutan yang salah. Ini memperbaikinya.

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:]
papercrane
sumber
1
Memasukkan di awal daftar adalah ide yang buruk. Mengapa tidak menggunakan struktur deque?
Sergey11g
1
Sayangnya tidak kompatibel dengan Python 3 ... mencoba mencari tahu alasannya.
Sherlock70
20

Kode yang akhirnya saya gunakan. Saya pikir ini yang terbaik sejauh ini:

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
Armin Ronacher
sumber
5
tidak persis menjawab pertanyaan itu.
sheki
13

Solusi sederhana dan cepat dengan 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()
dimitri
sumber
1
Ini mungkin jawaban tercepat ketika input bisa sangat besar (atau itu akan, jika itu menggunakan .rfindmetode untuk memindai mundur untuk baris baru, daripada melakukan byte pada suatu waktu memeriksa di tingkat Python; di CPython, mengganti kode tingkat Python dengan Panggilan bawaan C biasanya menang dengan banyak). Untuk input yang lebih kecil, dequedengan a maxlenlebih sederhana dan mungkin sama cepatnya.
ShadowRanger
4

Versi yang kompatibel dengan python3 yang bahkan lebih bersih yang tidak memasukkan tetapi menambahkan & membalikkan:

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

gunakan seperti ini:

with open(path, 'rb') as f:
    last_lines = tail(f, 3).decode('utf-8')
Hauke ​​Rehfeld
sumber
Tidak terlalu buruk - tetapi saya secara umum menyarankan untuk tidak menambahkan jawaban untuk pertanyaan berumur 10 tahun dengan banyak jawaban. Tetapi bantu saya: apa yang khusus untuk Python 3 dalam kode Anda?
usr2564301
Jawaban lain tidak benar-benar bekerja dengan baik :-) py3: lihat stackoverflow.com/questions/136168/…
Hauke ​​Rehfeld
3

Perbarui solusi @ papercrane ke python3. Buka file dengan open(filename, 'rb')dan:

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:]
Emilio
sumber
3

Posting jawaban atas perintah komentator atas jawaban saya untuk pertanyaan serupa di mana teknik yang sama digunakan untuk mengubah baris terakhir file, tidak hanya mendapatkannya.

Untuk file dengan ukuran signifikan, mmapadalah cara terbaik untuk melakukan ini. Untuk meningkatkan mmapjawaban yang ada , versi ini portabel antara Windows dan Linux, dan harus berjalan lebih cepat (meskipun tidak akan berfungsi tanpa beberapa modifikasi pada 32 bit Python dengan file dalam rentang GB, lihat jawaban lain untuk petunjuk tentang penanganan ini , dan untuk memodifikasi agar berfungsi pada 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)

Ini mengasumsikan jumlah garis berekor cukup kecil sehingga Anda dapat dengan aman membaca semuanya dalam memori sekaligus; Anda juga bisa menjadikan ini fungsi generator dan secara manual membaca satu baris sekaligus dengan mengganti baris terakhir dengan:

        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)

Terakhir, ini dibaca dalam mode biner (perlu digunakan mmap) sehingga memberikan strgaris (Py2) dan bytesgaris (Py3); jika Anda ingin unicode(Py2) atau str(Py3), pendekatan iteratif dapat diubah untuk memecahkan kode untuk Anda dan / atau memperbaiki baris baru:

        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

Catatan: Saya mengetik semuanya ini di mesin tempat saya tidak memiliki akses untuk menguji Python. Tolong beri tahu saya jika saya salah mengetik; ini cukup mirip dengan jawaban saya yang lain sehingga saya pikir itu harus bekerja, tetapi tweak (misalnya menangani offset) dapat menyebabkan kesalahan halus. Tolong beri tahu saya di komentar jika ada kesalahan.

ShadowRanger
sumber
3

Saya menemukan Popen di atas untuk menjadi solusi terbaik. Cepat dan kotor dan berfungsi Untuk python 2.6 pada mesin Unix saya menggunakan yang berikut ini

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 akan berisi n baris terakhir dari kode. untuk beralih melalui soutput baris demi baris lakukan:

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

berdasarkan jawaban terpilih S.Lott (25 Sep '08 di 21:43), tetapi tetap untuk file kecil.

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

Semoga ini bermanfaat.

Eyecue
sumber
2

Ada beberapa implementasi tail on pypi yang dapat Anda instal menggunakan pip:

  • mtFileUtil
  • multitail
  • log4tailer
  • ...

Tergantung pada situasi Anda, mungkin ada keuntungan menggunakan salah satu alat yang ada ini.

Travis Bear
sumber
Apakah Anda mengetahui adanya modul yang berfungsi pada Windows? Saya mencoba tailhead, tailertetapi mereka tidak berhasil. Juga mencoba mtFileUtil. Itu pada awalnya melempar kesalahan karena printpernyataan tidak memiliki tanda kurung (Saya di Python 3.6). Saya menambahkan itu reverse.pydan pesan kesalahan hilang tetapi ketika skrip saya memanggil modul ( mtFileUtil.tail(open(logfile_path), 5)), itu tidak mencetak apa pun.
Technext
2

Sederhana:

with open("test.txt") as f:
data = f.readlines()
tail = data[-2:]
print(''.join(tail)
Samba Siva Reddy
sumber
Ini benar-benar implementasi yang buruk. Pertimbangkan untuk menangani file besar, dan di mana n juga operasi yang sangat besar, terlalu mahal
Nivesh Krishna
1

Untuk efisiensi dengan file yang sangat besar (umum dalam situasi logfile di mana Anda mungkin ingin menggunakan tail), Anda umumnya ingin menghindari membaca seluruh file (bahkan jika Anda melakukannya tanpa membaca seluruh file ke dalam memori sekaligus) Namun, Anda lakukan perlu entah bagaimana bekerja diimbangi dalam garis daripada karakter. Salah satu kemungkinan adalah membaca mundur dengan seek () char by char, tetapi ini sangat lambat. Sebaliknya, lebih baik diproses dalam blok yang lebih besar.

Saya memiliki fungsi utilitas yang saya tulis beberapa waktu lalu untuk membaca file mundur yang dapat digunakan di sini.

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

[Sunting] Menambahkan versi yang lebih spesifik (tidak perlu mundur dua kali)

Brian
sumber
Tes cepat menunjukkan bahwa kinerjanya jauh lebih buruk daripada versi saya dari atas. Mungkin karena buffering Anda.
Armin Ronacher
Saya curiga itu karena saya melakukan beberapa upaya mundur, jadi tidak mendapatkan sebaik penggunaan buffer readahead. Namun, saya pikir ini mungkin lebih baik ketika tebakan Anda pada panjang garis tidak akurat (mis. Garis yang sangat besar), karena itu menghindari keharusan membaca kembali data dalam kasus ini.
Brian
1

Anda dapat pergi ke akhir file Anda dengan f.seek (0, 2) dan kemudian membacakan baris satu per satu dengan penggantian readline berikut ():

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
kelinci
sumber
1

Berdasarkan jawaban Eyecue (10 Jun '10 pada 21:28): kelas ini menambahkan metode head () dan tail () ke file objek.

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

Pemakaian:

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

Beberapa solusi ini memiliki masalah jika file tidak diakhiri dengan \ n atau memastikan baris pertama yang lengkap dibaca.

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
David Rogers
sumber
1

Berikut ini adalah implementasi yang cukup sederhana:

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()
GL2014
sumber
Contoh yang bagus! Bisakah Anda jelaskan penggunaan coba sebelum f.seek? Kenapa tidak sebelum with open? Juga, mengapa exceptAnda melakukan f.readlines()??
Sejujurnya, coba mungkin harus pergi dulu .. Saya tidak ingat memiliki alasan untuk tidak menangkap open () selain pada sistem Linux standar yang sehat, / etc / passwd harus selalu dapat dibaca. coba, maka dengan adalah urutan yang lebih umum.
GL2014
1

Ada modul yang sangat berguna yang dapat melakukan ini:

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)
Quinten Cabo
sumber
1

Solusi Lain

jika file txt Anda terlihat seperti ini: mouse snake cat lizard serigala dog

Anda bisa membalikkan file ini hanya dengan menggunakan pengindeksan array dalam 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)

hasil: kucing kadal serigala anjing

Blaine McMahon
sumber
1

Cara paling sederhana adalah dengan menggunakan deque:

from collections import deque

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

Saya harus membaca nilai tertentu dari baris terakhir file, dan menemukan utas ini. Daripada menciptakan kembali roda dengan Python, saya malah membuat skrip shell kecil, disimpan sebagai / usr / local / bin / get_last_netp:

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

Dan dalam program Python:

from subprocess import check_output

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

Bukan contoh pertama menggunakan deque, tetapi yang lebih sederhana. Ini umum: ini bekerja pada objek yang dapat diulang, bukan hanya file.

#!/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)
Hal Canary
sumber
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
Raj
sumber
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
moylop260
sumber
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]))
Y Kal
sumber
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) 
Kant Manapure
sumber
0

Pembaruan untuk jawaban yang diberikan oleh A.Coady

Bekerja dengan python 3 .

Ini menggunakan Pencarian Eksponensial dan hanya akan menyangga Nbaris dari belakang dan sangat efisien.

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))
itsjwala
sumber
-1

Setelah dipikir-pikir, ini mungkin secepat apa pun di sini.

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]

Jauh lebih sederhana. Dan itu tampaknya merobek dengan kecepatan yang baik.

S.Lott
sumber
Karena hampir semuanya di sini tidak bekerja dengan file log dengan lebih dari 30 MB atau lebih tanpa memuat jumlah memori yang sama ke dalam RAM;) Versi pertama Anda jauh lebih baik, tetapi untuk file uji di sini kinerjanya sedikit lebih buruk daripada saya dan itu tidak berfungsi dengan karakter baris baru yang berbeda.
Armin Ronacher
3
Saya salah. Versi 1 mengambil 0,00248908996582 untuk 10 ekor melalui kamus. Versi 2 mengambil 1.2963051796 untuk 10 ekor melalui kamus. Saya hampir memilih sendiri.
S.Lott
"Tidak bekerja dengan karakter baris baru yang berbeda." Ganti datacount ('\ n') dengan len (data.splitlines ()) jika itu penting.
S.Lott