Kuyruk benzeri bir dosyanın son n satırını al


181

Bir web uygulaması için bir günlük dosyası görüntüleyicisi yazıyorum ve bunun için günlük dosyasının satırları üzerinden sayfalamak istiyorum. Dosyadaki öğeler, alttaki en yeni öğeye göre satır bazındadır.

Bu yüzden alttan satırları tail()okuyabilen nve bir ofseti destekleyen bir yönteme ihtiyacım var . Ne geldi ben böyle görünüyor:

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

Bu makul bir yaklaşım mı? Günlük dosyalarını ofsetlerle kuyruğa almanın önerilen yolu nedir?


Sistemimde (linux SLES 10), sonuna göre arama yapmak bir IOError "sıfır olmayan son göreli aramaları yapamaz". Bu çözümü seviyorum ama dosya uzunluğunu ( seek(0,2)sonra tell()) almak ve başlangıca göre aramak için bu değeri kullanmak için değiştirdik.
Anne

2
Tebrikler - bu soru Kippo kaynak koduna dönüştürdü
Miles

Parametreleri openoluşturmak için kullanılan komuta fdosya nesnesini belirtilmelidir çünkü bağlı eğer f=open(..., 'rb')veya farklı işlenmesi gerekirf=open(..., 'rt')f
Igor Fobia

Yanıtlar:


123

Bu sizinkinden daha hızlı olabilir. Hat uzunluğu hakkında herhangi bir varsayımda bulunmaz. Doğru sayıda '\ n' karakter bulunana kadar dosyaya bir kerede bir blok geri döner.

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

- Pratik bir mesele olarak - böyle şeyleri asla bilemeyeceğiniz zaman çizgi uzunluğu ile ilgili zor varsayımları sevmiyorum.

Genellikle bu, döngüden ilk veya ikinci geçişteki son 20 satırı bulur. 74 karakterliğiniz gerçekten doğruysa, 2048 blok boyutunu yaparsınız ve neredeyse 20 satır çizersiniz.

Ayrıca, fiziksel işletim sistemi bloklarıyla hizalamayı incitmeye çalışırken çok fazla beyin kalori yakmıyorum. Bu üst düzey G / Ç paketlerini kullanarak, OS engelleme sınırlarına uyum sağlamaya çalışmanın herhangi bir performans sonucu göreceğinizden şüpheliyim. Daha düşük seviye G / Ç kullanıyorsanız, bir hızlanma görebilirsiniz.


GÜNCELLEME

Python 3.2 ve sonraki sürümler için, baytlardaki işlemi takip edin Metin dosyalarında ( mod dizesinde "b" olmadan açılanlar ), yalnızca dosyanın başlangıcına göre aramalara izin verilir (istisna dosya sonuna kadar aramaktadır) arama ile (0, 2)) .:

Örneğin: 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
Bu, küçük günlük dosyalarında başarısız oluyor - IOError: geçersiz argüman - f.seek (blok * 1024, 2)
ohnoes

1
Gerçekten çok güzel bir yaklaşım. Yukarıdaki kodun biraz değiştirilmiş bir versiyonunu kullandım ve bu tarifi buldum
Giampaolo Rodolà

6
Artık python 3.2'de çalışmıyor. Ben io.UnsupportedOperation: can't do nonzero end-relative seeksofseti 0 olarak değiştirebilirim, ama bu fonksiyonun amacını bozar.
Mantıksal Yanlışlık

4
@DavidEnglund Nedeni burada . Kısaca: metin modunda dosyanın sonuna göre aramaya izin verilmiyor, muhtemelen dosya içeriğinin kodunun çözülmesi gerektiğinden ve genel olarak, kodlanmış bayt dizisi içinde rasgele bir konum aramak, bu konumdan başlayarak Unicode'a kod çözmeyi deneyin. Bağlantıda sunulan öneri, dosyayı ikili modda açmayı denemek ve DecodeError istisnalarını yakalamak için kod çözmeyi kendiniz yapmaktır.
maksimum

6
BU KODU KULLANMAYIN. Python 2.7'deki bazı sınır durumlarındaki çizgileri bozar. Aşağıdaki @papercrane yanıtını düzeltir.
xApple

88

Python 2'de unix benzeri bir sistem yapabileceğinizi varsayar:

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 için şunları yapabilirsiniz:

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
Platformdan bağımsız olmalıdır. Ayrıca, soruyu okursanız, f'nin dosya benzeri bir nesne olduğunu göreceksiniz.
Armin Ronacher

40
soru platform bağımlılığının kabul edilemez olduğunu söylemiyor. Sorunun tam olarak ne istediğini yapmanın çok unixy (aradığınız şey olabilir ... kesinlikle benim için) yolunu verdiğinde bu neden iki downvotes hak ettiğini göremiyorum.
Shabbyrobe

3
Teşekkürler, bunu saf Python'da çözmek zorunda olduğumu düşünüyordum ama elindeyken UNIX yardımcı programlarını kullanmamanın bir nedeni yok, bu yüzden bununla gittim. Modern Python'daki FWIW, subprocess.check_output muhtemelen os.popen2'ye tercih edilir; çıktıyı dize olarak döndürdüğü ve sıfırdan farklı olmayan bir çıkış kodunda yükseldiği için işleri biraz basitleştirir.
mrooney

3
Bu platforma bağlı olmasına rağmen, sorulan şeyi yapmanın çok etkili bir yoludur ve bunu yapmanın son derece hızlı bir yoludur (Tüm dosyayı belleğe yüklemeniz gerekmez). @Shabbyrobe
earthmeLon

6
offset_total = str(n+offset)stdin,stdout = os.popen2("tail -n "+offset_total+" "+f)TypeErrors (cannot concatenate int+str)
Ofseti aşağıdaki

32

İşte cevabım. Saf piton. Timeit kullanarak oldukça hızlı görünüyor. 100.000 satır içeren bir günlük dosyasının 100 satırını kuyruklama:

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

İşte kod:

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
Zarif çözüm! Mı if len(lines_found) > lines:gerçekten gerekli? Değil mi loopkoşulu yakalamak o kadar iyi?
Maximilian Peters

Anladığım için bir soru: os.SEEK_ENDsadece açıklık için mi kullanılıyor? Bulduğum kadarıyla değeri sabittir (= 2). Dışarıda bırakabilmek için dışarıda bırakmayı merak ediyordum import os. Mükemmel çözüm için teşekkürler!
n1k31t4

2
@MaximilianPeters evet. Gereksiz. Ben yorum yaptım.
glenbot

@DexterMorgan yerine os.SEEK_ENDtam sayı eşdeğerini kullanabilirsiniz. Daha çok okunabilirlik için oradaydı.
glenbot

1
Ben iptal ettim, ama küçük bir nit var. Aramak sonra ilk satır okuma yüzden değiştirdi N _complete_lines almak için, eksik olabilir while len(lines_found) < linesiçin while len(lines_found) <= linesbenim kopyasında. Teşekkürler!
Graham Klyne

30

Dosyanın tamamını okumak kabul edilebilir bir deque kullanın.

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

2.6'dan önce, deques'in maxlen seçeneği yoktu, ancak uygulanması yeterince kolay.

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

Dosyayı sondan okumak gerekiyorsa dörtnala (üstel olarak da bilinir) bir arama kullanın.

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

Bu alt işlev neden çalışıyor? pos *= 2tamamen keyfi görünüyor. Önemi nedir?
2mac

1
@ 2mac Üstel Arama . Dosya sonundan itibaren yinelenen şekilde okur, yeterli satır bulunana kadar her seferinde okunan miktarı ikiye katlar.
A. Coady

Karakter uzunluğu değişken olduğundan, sonuna kadar okumak için çözüm UTF-8 ile kodlanmış dosyaları desteklemeyeceğini düşünüyorum ve doğru yorumlanamayan bazı tek ofset (muhtemelen) arazi olabilir.
Mike

ne yazık ki dörtnala arama çözümünüz python 3 için çalışmıyor. Çünkü f.seek () negatif ofset almaz. Kodunuzu python 3 bağlantısı
itsjwala

25

S.Lott'un yukarıdaki cevabı neredeyse benim için çalışıyor ama sonuçta bana kısmi çizgiler veriyor. Veri okuma bloklarını ters sırada tuttuğu için blok sınırlarındaki verileri bozduğu ortaya çıkıyor. '' .Join (veri) çağrıldığında, bloklar yanlış sıradadır. Bu sorunu düzeltir.

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
Listenin başına eklemek kötü bir fikirdir. Neden deque yapısını kullanmıyorsunuz?
Sergey11g

1
Ne yazık ki Python 3 uyumlu değil ... nedenini anlamaya çalışıyor.
Sherlock70

20

Kodu kullanarak sona erdi. Bence bu şimdiye kadarki en iyisi:

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
soruyu tam olarak cevaplamıyor.
sheki

13

Mmap ile basit ve hızlı çözüm:

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
Bu, girişin büyük olabileceği en hızlı yanıttır (veya .rfindPython düzeyinde bir seferde bayt yapmak yerine yeni satırlar için geriye doğru tarama yöntemini kullansaydı ; CPython'da Python seviye kodunu değiştirmek C dahili çağrıları genellikle çok kazanır). Daha küçük girişler için deque, a maxlenişareti daha basit ve muhtemelen benzer şekilde hızlıdır.
ShadowRanger

4

Eklenmeyen, ancak ekleyen ve tersine çeviren daha temiz bir python3 uyumlu sürüm:

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

şöyle kullanın:

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

Çok perişan değil - ama genel olarak 10 yaşındaki bir soruya bolca cevap içeren bir cevap eklememenizi tavsiye ederim. Ama bana yardım et: Kodunuzdaki Python 3'e özgü nedir?
usr2564301

Diğer cevaplar tam olarak işe yaramıyordu :-) py3: bkz. Stackoverflow.com/questions/136168/…
Hauke ​​Rehfeld

3

@Papercrane çözümünü python3'e güncelleyin. Dosyayı open(filename, 'rb')ve ile açın :

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

Bir dosyanın son satırını değiştirmek için değil, aynı tekniğin kullanıldığı benzer bir soruya cevabım üzerine yorum yapanların isteği üzerine bir cevap göndermek .

Önemli boyutta bir dosya mmapiçin bunu yapmanın en iyi yolu budur. Mevcut mmapcevabı iyileştirmek için , bu sürüm Windows ve Linux arasında taşınabilir ve daha hızlı çalışmalıdır (GB aralığında dosyalar bulunan 32 bit Python'da bazı değişiklikler yapılmadan çalışmasa da, bununla ilgili ipuçları için diğer cevaba bakınız. ve Python 2'de çalışacak şekilde değiştirmek için ).

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)

Bu, kuyruklanan satır sayısının, hepsini bir kerede güvenli bir şekilde belleğe okuyabileceğiniz kadar küçük olduğunu varsayar; ayrıca bunu bir jeneratör işlevi haline getirebilir ve son satırı aşağıdaki ile değiştirerek bir kerede manuel olarak okuyabilirsiniz:

        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)

Son olarak, bu ikili modda (kullanımı gerekli mmap) okunur, böylece strsatırlar (Py2) ve bytessatırlar (Py3) verir; unicode(Py2) veya str(Py3) istiyorsanız , yinelemeli yaklaşım sizin için kod çözme ve / veya yeni satırları düzeltme için ayarlanabilir:

        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

Not: Bunu test etmek için Python'a erişemediğim bir makineye yazdım. Bir şey yazıp yazmadığımı lütfen bize bildirin; Buna benzer yeterince oldu benim diğer yanıt Bunu düşünmek onu çalışmalıdır, ancak ince ayarlar (örneğin bir taşıma offset) ince hatalara yol açabilir. Herhangi bir hata varsa lütfen yorumlarda bana bildirin.


3

Yukarıdaki Popen'i en iyi çözüm olarak buldum. Hızlı ve kirli ve çalışıyor Unix makinesinde python 2.6 için aşağıdakileri kullandım

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 kodun son n satırını içerecektir. soutput üzerinden satır satır yineleme yapmak için:

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

2

S.Lott'un en çok oy alan cevabına dayanarak (21 Eylül 43: 43'te), ancak küçük dosyalar için düzeltildi.

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

Umarım faydalıdır.


2

Pip kullanarak kurabileceğiniz pypi üzerinde kuyruk mevcut bazı uygulamalar vardır:

  • mtFileUtil
  • multitail
  • log4tailer
  • ...

Durumunuza bağlı olarak, bu mevcut araçlardan birini kullanmanın avantajları olabilir.


Windows üzerinde çalışan herhangi bir modülün farkında mısınız? Denedim tailhead, tailerama işe yaramadı. Ayrıca denedi mtFileUtil. Başlangıçta hata atıyordu çünkü printifadelerde parantez yoktu (Python 3.6'dayım). Bunları ekledim reverse.pyve hata mesajları gitti ama benim komut modül ( mtFileUtil.tail(open(logfile_path), 5)) çağırdığında, hiçbir şey yazdırmaz.
Technext

2

Basit:

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

Bu tamamen kötü bir uygulama. Büyük dosyaları ve n'nin de çok büyük, çok pahalı bir işlem olduğunu
düşünün

1

Çok büyük dosyalarla (kuyruk kullanmak isteyebileceğiniz günlük dosyası durumlarında yaygın olarak) verimlilik için, genellikle tüm dosyayı okumaktan kaçınmak istersiniz (tüm dosyayı bir kerede belleğe okumadan yapsanız bile) Ancak, ofseti bir şekilde karakterlerden ziyade satırlar halinde çözmeniz gerekir. Bir olasılık char () char ile char ile geriye doğru okumaktır, ancak bu çok yavaştır. Bunun yerine, daha büyük bloklarda işlemek daha iyidir.

Burada kullanılabilecek dosyaları geriye doğru okumak için bir süre önce yazdığım bir yardımcı program işlevim var.

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

[Düzenle] Daha spesifik bir sürüm eklendi (iki kez geri gitme ihtiyacını ortadan kaldırır)


Hızlı testler, bunun yukarıdaki sürümümden çok daha kötü performans gösterdiğini gösteriyor. Muhtemelen arabelleğe aldığınız için.
Armin Ronacher

Bunun geriye doğru birden fazla arama yaptığımdan şüpheleniyorum, bu yüzden readahead tamponunu iyi kullanmıyorlar. Ancak, hat uzunluğundaki tahmininiz doğru olmadığında (örneğin çok büyük çizgiler) daha iyi olabileceğini düşünüyorum, çünkü bu durumda verileri yeniden okumak zorunda kalmaz.
Brian

1

f.seek (0, 2) ile dosyanızın sonuna gidebilir ve sonra readline () için aşağıdaki yedek ile satırları tek tek okuyabilirsiniz:

def readline_backwards(self, f):
    backline = ''
    last = ''
    while not last == '\n':
        backline = last + backline
        if f.tell() <= 0:
            return backline
        f.seek(-1, 1)
        last = f.read(1)
        f.seek(-1, 1)
    backline = last
    last = ''
    while not last == '\n':
        backline = last + backline
        if f.tell() <= 0:
            return backline
        f.seek(-1, 1)
        last = f.read(1)
        f.seek(-1, 1)
    f.seek(1, 1)
    return backline

1

Eyecue cevabına göre (10: 10'da 21:28): bu sınıf dosya nesnesine head () ve tail () yöntemini ekler.

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

Kullanımı:

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

1

Dosya \ n ile bitmezse veya ilk satırın tamamının okunmasını sağlarsa bu çözümlerin birçoğunda sorun vardır.

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

İşte oldukça basit bir uygulama:

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

Harika bir örnek! Önce denemenin kullanımını açıklar f.seekmısınız? Neden önce with open? Ayrıca, neden exceptbir f.readlines()??

Dürüst olmak gerekirse, denemenin önce gitmesi gerekir .. Sağlıklı bir standart Linux sisteminden ziyade open () 'i yakalamamak için bir nedenim olduğunu hatırlamıyorum, / etc / passwd her zaman okunabilir olmalıdır. deneyin, o zaman ile daha yaygın düzen.
GL2014

1

Bunu yapabilen çok kullanışlı bir modül var:

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

Başka bir çözüm

txt dosyanız şu şekilde görünüyorsa: fare yılan kedi kertenkele kurt köpeği

bu dosyayı python 'da dizi indekslemeyi kullanarak tersine çevirebilirsiniz' ''

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)

sonuç: köpek kurt kertenkele kedi


1

En basit yol kullanmaktır deque:

from collections import deque

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

0

Bir dosyanın son satırından belirli bir değer okumak zorunda kaldı ve bu iş parçacığı tökezledi. Python'daki tekerleği yeniden icat etmek yerine, / usr / local / bin / get_last_netp olarak kaydedilen küçük bir kabuk komut dosyası ile sonuçlandım:

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

Ve Python programında:

from subprocess import check_output

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

0

Deque kullanan ilk örnek değil, daha basit bir örnek. Bu geneldir: sadece bir dosya üzerinde değil, yinelenebilir herhangi bir nesne üzerinde çalışır.

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

A.Coady tarafından verilen cevap için güncelleme

Python 3 ile çalışır .

Bu, Üstel Arama kullanır ve yalnızca Narkadan gelen satırları arabelleğe alır ve çok etkilidir.

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

İkinci düşüncede, bu muhtemelen burada herhangi bir şey kadar hızlı.

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]

Çok daha basit. Ve iyi bir hızda kopuyor gibi görünüyor.


Buradaki hemen hemen her şey, RAM'e aynı miktarda bellek yüklemeden 30 MB'den fazla günlük dosyaları ile çalışmadığından;) İlk sürümünüz çok daha iyi, ancak burada test dosyaları için benimkinden biraz daha kötü performans gösteriyor ve farklı yeni satır karakterleriyle çalışmaz.
Armin Ronacher

3
Ben hatalıydım. Sürüm 1, sözlük aracılığıyla 10 kuyruk için 0.00248908996582 aldı. Sürüm 2 sözlükte 10 kuyruk için 1.2963051796 aldı. Neredeyse oyumu atarım.
S.Lott

"farklı yeni satır karakterleriyle çalışmaz." Önemliyse datacount ('\ n') yerine len (data.splitlines ()) yazın.
S.Lott
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.