tailと同様に、ファイルの最後のn行を取得します


181

私はWebアプリケーションのログファイルビューアを作成しています。そのために、ログファイルの行にページ番号を付けたいと思います。ファイル内のアイテムは、一番下にある最新のアイテムを基にした行です。

したがって、下から行をtail()読み取ることができn、オフセットをサポートするメソッドが必要です。私が思いついたのはこのようなものです:

def tail(f, n, offset=0):
    """Reads a n lines from f with an offset of offset lines."""
    avg_line_length = 74
    to_read = n + offset
    while 1:
        try:
            f.seek(-(avg_line_length * to_read), 2)
        except IOError:
            # woops.  apparently file is smaller than what we want
            # to step back, go to the beginning instead
            f.seek(0)
        pos = f.tell()
        lines = f.read().splitlines()
        if len(lines) >= to_read or pos == 0:
            return lines[-to_read:offset and -offset or None]
        avg_line_length *= 1.3

これは合理的なアプローチですか?オフセットを含むログファイルをテールにする推奨方法は何ですか?


私のシステム(Linux SLES 10)では、末尾を基準にしてシークすると、IOErrorが発生します。私はこのソリューションが好きですが、ファイルの長さ(seek(0,2)次にtell())を取得するように変更し、その値を使用して先頭からの相対位置を探します。
アン

2
おめでとうございます-この質問はKippoのソースコードに組み込まれました
Miles

ファイルオブジェクトのopen生成に使用するコマンドのパラメーターをf指定する必要があります。これは、処理方法が異なるかどうf=open(..., 'rb')かによって異なり ますf=open(..., 'rt')f
Igor Fobia

回答:


123

これはあなたのものより速いかもしれません。行の長さについては何も想定していません。正しい数の「\ n」文字が見つかるまで、一度に1ブロックずつファイルをさかのぼります。

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

実際には、そのようなことを決して知ることができないとき、私は行の長さについてのトリッキーな仮定が好きではありません。

通常、これにより、ループの最初または2番目のパスで最後の20行が見つかります。74文字のものが実際に正確である場合は、ブロックサイズを2048にすると、ほとんどすぐに20行になります。

また、物理的なOSブロックとの整合を調整するために、脳のカロリーをあまり消費しません。これらの高レベルのI / Oパッケージを使用すると、OSブロックの境界に合わせようとすることによるパフォーマンスへの影響は見られないでしょう。下位レベルのI / Oを使用すると、速度が向上する場合があります。


更新

Python 3.2以降の場合は、バイトのプロセスをテキストファイル(モード文字列に「b」なしで開いたもの)として実行します。ファイルの先頭を基準にしたシークのみが許可されます(例外は、ファイルの最後までシークします) seek(0、2)を使用):

例えば: f = open('C:/.../../apache_logs.txt', 'rb')

 def tail(f, lines=20):
    total_lines_wanted = lines

    BLOCK_SIZE = 1024
    f.seek(0, 2)
    block_end_byte = f.tell()
    lines_to_go = total_lines_wanted
    block_number = -1
    blocks = []
    while lines_to_go > 0 and block_end_byte > 0:
        if (block_end_byte - BLOCK_SIZE > 0):
            f.seek(block_number*BLOCK_SIZE, 2)
            blocks.append(f.read(BLOCK_SIZE))
        else:
            f.seek(0,0)
            blocks.append(f.read(block_end_byte))
        lines_found = blocks[-1].count(b'\n')
        lines_to_go -= lines_found
        block_end_byte -= BLOCK_SIZE
        block_number -= 1
    all_read_text = b''.join(reversed(blocks))
    return b'\n'.join(all_read_text.splitlines()[-total_lines_wanted:])

13
これは小さなログファイルでは失敗します-IOError:無効な引数-f.seek(block *
1024、2

1
とても素晴らしいアプローチです。上記のコードを少し変更したバージョンを使用して、次のレシピを作成しました:code.activestate.com/recipes/577968-log-watcher-tail-f-log
GiampaoloRodolà11

6
Python 3.2では動作しなくなりました。私はそうだio.UnsupportedOperation: can't do nonzero end-relative seeks、私はオフセットを0に変更することができますが、それは機能の目的に反し。
論理的誤り:

4
@DavidEnglund理由はこちらです。簡単に言うと、おそらくファイルの内容をデコードする必要があるため、テキストモードではファイルの末尾を基準にしたシークは許可されていません。その位置から開始してUnicodeへのデコードを試みます。リンクで提示されている提案は、バイナリモードでファイルを開いて自分でデコードし、DecodeError例外をキャッチすることです。
最大

6
このコードを使用しないでください。Python 2.7の一部のボーダーケースで行が破損します。以下の@papercraneからの答えはそれを修正します。
xApple 2013

88

Python 2で実行できるUNIXライクなシステムを想定しています。

import os
def tail(f, n, offset=0):
  stdin,stdout = os.popen2("tail -n "+n+offset+" "+f)
  stdin.close()
  lines = stdout.readlines(); stdout.close()
  return lines[:,-offset]

Python 3の場合は、次のようにします。

import subprocess
def tail(f, n, offset=0):
    proc = subprocess.Popen(['tail', '-n', n + offset, f], stdout=subprocess.PIPE)
    lines = proc.stdout.readlines()
    return lines[:, -offset]

5
プラットフォームに依存しない必要があります。さらに、質問を読むと、fがオブジェクトのようなファイルであることがわかります。
Armin Ronacher、

40
問題は、プラットフォームへの依存が受け入れられないことではありません。質問が正確に答える方法を提供するのに、これが2つの反対投票に値する理由がわかりません。
Shabbyrobe

3
ありがとう、私はこれを純粋なPythonで解決する必要があると思っていましたが、手元にあるときにUNIXユーティリティを使用しない理由はないので、これを使いました。最近のPythonのFWIWでは、os.popen2よりもsubprocess.check_outputの方が望ましいと思われます。出力を文字列として返し、ゼロ以外の終了コードを生成するため、処理が少し単純になります。
mrooney 2013年

3
これはプラットフォームに依存しますが、要求されたことを実行する非常に効率的な方法であり、非常に高速な方法です(ファイル全体をメモリにロードする必要はありません)。@Shabbyrobe
earthmeLon

6
あなたは次のようにオフセットを事前計算する場合がありますoffset_total = str(n+offset)と、この行を置き換えstdin,stdout = os.popen2("tail -n "+offset_total+" "+f)ないようにするにはTypeErrors (cannot concatenate int+str)
AddingColor

32

これが私の答えです。純粋なpython。timeitを使用すると、かなり高速に見えます。100,000行のログファイルを100行追跡する:

>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=10)
0.0014600753784179688
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=100)
0.00899195671081543
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=1000)
0.05842900276184082
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=10000)
0.5394978523254395
>>> timeit.timeit('tail.tail(f, 100, 4098)', 'import tail; f = open("log.txt", "r");', number=100000)
5.377126932144165

これがコードです:

import os


def tail(f, lines=1, _buffer=4098):
    """Tail a file and get X lines from the end"""
    # place holder for the lines found
    lines_found = []

    # block counter will be multiplied by buffer
    # to get the block size from the end
    block_counter = -1

    # loop until we find X lines
    while len(lines_found) < lines:
        try:
            f.seek(block_counter * _buffer, os.SEEK_END)
        except IOError:  # either file is too small, or too many lines requested
            f.seek(0)
            lines_found = f.readlines()
            break

        lines_found = f.readlines()

        # we found enough lines, get out
        # Removed this line because it was redundant the while will catch
        # it, I left it for history
        # if len(lines_found) > lines:
        #    break

        # decrement the block counter to get the
        # next X bytes
        block_counter -= 1

    return lines_found[-lines:]

3
エレガントなソリューション!ある if len(lines_found) > lines:本当に必要?loop条件もそれをキャッチしませんか?
Maximilian Peters

私の理解に対する質問:os.SEEK_END単に明確にするために使用されていますか?私が見つけた限りでは、その値は一定(= 2)です。私はそれを除外することができるようにそれを除外することについて疑問に思っていましたimport os。素晴らしい解決策をありがとう!
n1k31t4 2017年

2
@MaximilianPetersはい。それは必要はありません。コメントアウトしました。
glenbot 2017年

@DexterMorgan os.SEEK_ENDは、同等の整数で置き換えることができます。それは主に読みやすさのためにありました。
glenbot 2017年

1
私は賛成票を投じましたが、小さなニットがあります。シーク後、最初に読み取った行が不完全な場合があるため、N _complete_linesを取得するwhile len(lines_found) < lineswhile len(lines_found) <= linesは、コピーのをに変更しました。ありがとう!
グラハムクライン

30

ファイル全体の読み取りが許容できる場合は、両端キューを使用します。

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

2.6より前は、dequeにmaxlenオプションはありませんでしたが、実装するのは簡単です。

import itertools
def maxque(items, size):
    items = iter(items)
    q = deque(itertools.islice(items, size))
    for item in items:
        del q[0]
        q.append(item)
    return q

ファイルを最後から読み取る必要がある場合は、ギャロップ(別名指数)検索を使用します。

def tail(f, n):
    assert n >= 0
    pos, lines = n+1, []
    while len(lines) <= n:
        try:
            f.seek(-pos, 2)
        except IOError:
            f.seek(0)
            break
        finally:
            lines = list(f)
        pos *= 2
    return lines[-n:]

なぜそのボトム関数が機能するのですか?pos *= 2完全に恣意的です。その意味は何ですか?
2mac、2014

1
@ 2mac 指数検索。十分な行が見つかるまで、ファイルの終わりから繰り返し読み取り、毎回読み取られる量を2倍にします。
A. Coady 2015年

文字の長さが可変であるため、最後から読み取るソリューションはUTF-8でエンコードされたファイルをサポートせず、正しく解釈できない奇妙なオフセットに到達する可能性があります(可能性があります)。
マイク

残念ながら、 ギャロッピング検索ソリューションはpython 3では機能しません。f.seek()は負のオフセットをとらないためです。私はあなたのコードを更新してそれをpython 3 リンク
itsjwala '30 / 07/17

25

上記のS.Lottの答えはほとんど私にとってはうまくいきますが、最終的には部分的な行が表示されます。データが読み取りブロックを逆の順序で保持しているため、ブロック境界のデータが破損していることがわかります。'' .join(data)が呼び出されると、ブロックの順序が間違っています。これはそれを修正します。

def tail(f, window=20):
    """
    Returns the last `window` lines of file `f` as a list.
    f - a byte file-like object
    """
    if window == 0:
        return []
    BUFSIZ = 1024
    f.seek(0, 2)
    bytes = f.tell()
    size = window + 1
    block = -1
    data = []
    while size > 0 and bytes > 0:
        if bytes - BUFSIZ > 0:
            # Seek back one whole BUFSIZ
            f.seek(block * BUFSIZ, 2)
            # read BUFFER
            data.insert(0, f.read(BUFSIZ))
        else:
            # file too small, start from begining
            f.seek(0,0)
            # only read what was not read
            data.insert(0, f.read(bytes))
        linesFound = data[0].count('\n')
        size -= linesFound
        bytes -= BUFSIZ
        block -= 1
    return ''.join(data).splitlines()[-window:]

1
リストの最初に挿入するのは悪い考えです。deque構造を使用しないのはなぜですか?
Sergey11g 2017

1
残念ながら、Python 3互換ではありません...理由を理解しようとしています。
Sherlock70

20

結局使用したコード。これが今のところ最高だと思います:

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
質問に正確に答えていません。
シェキ

13

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
これはおそらく、入力が膨大になる可能性がある場合(または.rfind、Pythonレベルで一度に1バイトずつチェックを実行するのではなく、メソッドを使用して改行を逆方向にスキャンする場合は、最速の答えです。CPythonでは、PythonレベルのコードをCの組み込み呼び出しは、通常、多くの点で優先されます。入力が小さい場合、dequewith a maxlenはより単純で、おそらく同じように高速です。
ShadowRanger、2015年

4

挿入せずに追加と反転を行う、よりクリーンなpython3互換バージョン:

def tail(f, window=1):
    """
    Returns the last `window` lines of file `f` as a list of bytes.
    """
    if window == 0:
        return b''
    BUFSIZE = 1024
    f.seek(0, 2)
    end = f.tell()
    nlines = window + 1
    data = []
    while nlines > 0 and end > 0:
        i = max(0, end - BUFSIZE)
        nread = min(end, BUFSIZE)

        f.seek(i)
        chunk = f.read(nread)
        data.append(chunk)
        nlines -= chunk.count(b'\n')
        end -= nread
    return b'\n'.join(b''.join(reversed(data)).splitlines()[-window:])

次のように使用します。

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

粗末すぎることはありませんが、一般的には、10年前の質問に多くの回答を含めて回答を追加しないことをお勧めします。しかし、私を助けてください:あなたのコードのPython 3に固有のものは何ですか?
usr2564301 2018年

他の回答は正確にうまく機能しませんでした:-) py3:stackoverflow.com/questions/136168/…を
Hauke Rehfeld

3

@papercraneソリューションをpython3に更新します。open(filename, 'rb')andでファイルを開きます。

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

同様の質問への私の回答に対するコメント投稿者の要請で回答を投稿する。ファイルを取得するだけでなく、同じ手法を使用してファイルの最後の行を変更しました。

かなりのサイズのファイルの場合、mmapこれがこれを行う最善の方法です。既存のmmap回答を改善するために、このバージョンはWindowsとLinuxの間で移植可能であり、より高速に実行する必要があります(GB範囲のファイルを使用する32ビットPythonでは変更を加えないと動作しませんが、これを処理するためのヒントについては、他の回答を参照してください、およびPython 2で動作するように変更する場合)。

import io  # Gets consistent version of open for both Py2.7 and Py3.x
import itertools
import mmap

def skip_back_lines(mm, numlines, startidx):
    '''Factored out to simplify handling of n and offset'''
    for _ in itertools.repeat(None, numlines):
        startidx = mm.rfind(b'\n', 0, startidx)
        if startidx < 0:
            break
    return startidx

def tail(f, n, offset=0):
    # Reopen file in binary mode
    with io.open(f.name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # len(mm) - 1 handles files ending w/newline by getting the prior line
        startofline = skip_back_lines(mm, offset, len(mm) - 1)
        if startofline < 0:
            return []  # Offset lines consumed whole file, nothing to return
            # If using a generator function (yield-ing, see below),
            # this should be a plain return, no empty list

        endoflines = startofline + 1  # Slice end to omit offset lines

        # Find start of lines to capture (add 1 to move from newline to beginning of following line)
        startofline = skip_back_lines(mm, n, startofline) + 1

        # Passing True to splitlines makes it return the list of lines without
        # removing the trailing newline (if any), so list mimics f.readlines()
        return mm[startofline:endoflines].splitlines(True)
        # If Windows style \r\n newlines need to be normalized to \n, and input
        # is ASCII compatible, can normalize newlines with:
        # return mm[startofline:endoflines].replace(os.linesep.encode('ascii'), b'\n').splitlines(True)

これは、テイルされた行の数が少なく、一度にすべてを安全にメモリに読み込むことができると想定しています。これをジェネレーター関数にして、最後の行を次のように置き換えることにより、一度に1行ずつ手動で読み取ることもできます。

        mm.seek(startofline)
        # Call mm.readline n times, or until EOF, whichever comes first
        # Python 3.2 and earlier:
        for line in itertools.islice(iter(mm.readline, b''), n):
            yield line

        # 3.3+:
        yield from itertools.islice(iter(mm.readline, b''), n)

最後に、これはバイナリモードで読み取る(を使用するmmapために必要)ので、str行(Py2)とbytes行(Py3)が得られます。unicode(Py2)またはstr(Py3)が必要な場合は、反復アプローチを微調整して、デコードしたり、改行を修正したりできます。

        lines = itertools.islice(iter(mm.readline, b''), n)
        if f.encoding:  # Decode if the passed file was opened with a specific encoding
            lines = (line.decode(f.encoding) for line in lines)
        if 'b' not in f.mode:  # Fix line breaks if passed file opened in text mode
            lines = (line.replace(os.linesep, '\n') for line in lines)
        # Python 3.2 and earlier:
        for line in lines:
            yield line
        # 3.3+:
        yield from lines

注:テストするためにPythonにアクセスできないマシンでこれをすべて入力しました。入力ミスがありましたらお知らせください。これは、他の回答と十分に似ていて機能すると思いますが、微調整(たとえばの処理offset)により、微妙なエラーが発生する可能性があります。間違いがあればコメントで教えてください。


3

上記のPopenが最良のソリューションであることがわかりました。それは速くて汚いです、そしてそれは動作しますUnixマシン上のPython 2.6では私は以下を使用しました

def GetLastNLines(self, n, fileName):
    """
    Name:           Get LastNLines
    Description:        Gets last n lines using Unix tail
    Output:         returns last n lines of a file
    Keyword argument:
    n -- number of last lines to return
    filename -- Name of the file you need to tail into
    """
    p = subprocess.Popen(['tail','-n',str(n),self.__fileName], stdout=subprocess.PIPE)
    soutput, sinput = p.communicate()
    return soutput

soutputには、コードの最後のn行が含まれます。行ごとにsoutputを繰り返すには、次のようにします。

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

2

S.Lottのトップ投票の回答(2008年9月25日21:43)に基づいていますが、小さなファイル用に修正されています。

def tail(the_file, lines_2find=20):  
    the_file.seek(0, 2)                         #go to end of file
    bytes_in_file = the_file.tell()             
    lines_found, total_bytes_scanned = 0, 0
    while lines_2find+1 > lines_found and bytes_in_file > total_bytes_scanned: 
        byte_block = min(1024, bytes_in_file-total_bytes_scanned)
        the_file.seek(-(byte_block+total_bytes_scanned), 2)
        total_bytes_scanned += byte_block
        lines_found += the_file.read(1024).count('\n')
    the_file.seek(-total_bytes_scanned, 2)
    line_list = list(the_file.readlines())
    return line_list[-lines_2find:]

    #we read at least 21 line breaks from the bottom, block by block for speed
    #21 to ensure we don't get a half line

これがお役に立てば幸いです。


2

pypiを使用してインストールできる、pypi上のtailの既存の実装がいくつかあります。

  • mtFileUtil
  • マルチテール
  • log4tailer
  • ...

状況によっては、これらの既存のツールのいずれかを使用する利点がある場合があります。


Windowsで動作するモジュールを知っていますか?私が試したtailheadtailerしかし、彼らは動作しませんでした。また試してみましたmtFileUtilprintステートメントに括弧がないため、最初はエラーをスローしていました(私はPython 3.6を使用しています)。これらを追加したところreverse.py、エラーメッセージは表示されなくなりましたが、スクリプトがモジュール(mtFileUtil.tail(open(logfile_path), 5))を呼び出しても何も出力されません。
Technext 2018

2

シンプル:

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

これは完全に悪い実装です。巨大なファイルを処理することを検討してください。また、nも巨大で高価な操作です
Nivesh Krishna

1

非常に大きなファイルで効率を上げるため(tailを使用する可能性があるログファイルの状況では一般的です)、通常はファイル全体を読み取らないようにします(一度にファイル全体をメモリに読み込まずに行う場合でも)。ただし、どういうわけか、文字ではなく行単位でオフセットを計算する必要があります。1つの可能性は、シークを1文字ずつ逆方向に読み取ることですが、これは非常に低速です。代わりに、大きなブロックで処理する方が良いでしょう。

ここで使用できるファイルを逆方向​​に読み取るために少し前に書いたユーティリティ関数があります。

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

[編集]より具体的なバージョンを追加しました(2度反転する必要はありません)


簡単なテストは、これが上からの私のバージョンよりもかなり悪いことを示しています。おそらくあなたのバッファリングのためです。
Armin Ronacher

逆方向に複数のシークを行っているため、先読みバッファが十分に活用されていないためと考えられます。ただし、この場合はデータを再度読み取る必要がなくなるため、行の長さの推測が正確でない場合(たとえば、非常に長い行)の方が良いと思います。
ブライアン

1

f.seek(0、2)を使用してファイルの最後に移動し、次のreadline()の代わりに1行ずつ読み取ることができます。

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の回答に基づいて(Jun 10 '10 at 21:28):このクラスは、head()およびtail()メソッドをファイルオブジェクトに追加します。

class File(file):
    def head(self, lines_2find=1):
        self.seek(0)                            #Rewind file
        return [self.next() for x in xrange(lines_2find)]

    def tail(self, lines_2find=1):  
        self.seek(0, 2)                         #go to end of file
        bytes_in_file = self.tell()             
        lines_found, total_bytes_scanned = 0, 0
        while (lines_2find+1 > lines_found and
               bytes_in_file > total_bytes_scanned): 
            byte_block = min(1024, bytes_in_file-total_bytes_scanned)
            self.seek(-(byte_block+total_bytes_scanned), 2)
            total_bytes_scanned += byte_block
            lines_found += self.read(1024).count('\n')
        self.seek(-total_bytes_scanned, 2)
        line_list = list(self.readlines())
        return line_list[-lines_2find:]

使用法:

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

1

これらの解決策のいくつかは、ファイルが\ nで終わっていない場合、または完全な最初の行が読み取られていることを確認する場合に問題があります。

def tail(file, n=1, bs=1024):
    f = open(file)
    f.seek(-1,2)
    l = 1-f.read(1).count('\n') # If file doesn't end in \n, count it anyway.
    B = f.tell()
    while n >= l and B > 0:
            block = min(bs, B)
            B -= block
            f.seek(B, 0)
            l += f.read(block).count('\n')
    f.seek(B, 0)
    l = min(l,n) # discard first (incomplete) line if l > n
    lines = f.readlines()[-l:]
    f.close()
    return lines

1

これはかなり単純な実装です:

with open('/etc/passwd', 'r') as f:
  try:
    f.seek(0,2)
    s = ''
    while s.count('\n') < 11:
      cur = f.tell()
      f.seek((cur - 10))
      s = f.read(10) + s
      f.seek((cur - 10))
    print s
  except Exception as e:
    f.readlines()

素晴らしい例!の前にtryの使用について説明していただけますf.seekか?どうしてwith open?また、なぜexceptあなたはf.readlines()??

正直なところ、最初に試してみる必要があります。健全な標準Linuxシステム以外でopen()をキャッチしない理由を覚えていないので、/ etc / passwdは常に読み取り可能である必要があります。試してみてください、それからより一般的な順序です。
GL2014

1

これを行うことができる非常に便利なモジュールがあります:

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

別の解決策

あなたのtxtファイルが次のようになった場合:マウスヘビ猫トカゲオオカミ犬

あなたは単に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)

結果:犬オオカミトカゲ猫


1

最も簡単な方法は次のようにすることdequeです:

from collections import deque

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

0

ファイルの最後の行から特定の値を読み取る必要があり、このスレッドに出くわしました。Pythonでホイールを再発明するのではなく、小さなシェルスクリプトを作成して、/ usr / local / bin / get_last_netpとして保存しました。

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

そしてPythonプログラムでは:

from subprocess import check_output

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

0

dequeを使用する最初の例ではなく、より単純な例。これは一般的なものです。ファイルだけでなく、反復可能なオブジェクトで機能します。

#!/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による回答の更新

作品のpython 3

これは指数検索を使用しN、後ろからの行のみをバッファーし、非常に効率的です。

import time
import os
import sys

def tail(f, n):
    assert n >= 0
    pos, lines = n+1, []

    # set file pointer to end

    f.seek(0, os.SEEK_END)

    isFileSmall = False

    while len(lines) <= n:
        try:
            f.seek(f.tell() - pos, os.SEEK_SET)
        except ValueError as e:
            # lines greater than file seeking size
            # seek to start
            f.seek(0,os.SEEK_SET)
            isFileSmall = True
        except IOError:
            print("Some problem reading/seeking the file")
            sys.exit(-1)
        finally:
            lines = f.readlines()
            if isFileSmall:
                break

        pos *= 2

    print(lines)

    return lines[-n:]




with open("stream_logs.txt") as f:
    while(True):
        time.sleep(0.5)
        print(tail(f,2))

-1

考え直してみると、これはおそらくここにあるものと同じくらい高速です。

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]

ずっと簡単です。そして、それは良いペースで引き裂かれているようです。


ここにあるほとんどすべてが、同じ量のメモリをRAMにロードしないと、30 MBを超えるログファイルでは機能しません。異なる改行文字では機能しません。
Armin Ronacher、

3
私は間違っていた。バージョン1は、ディクショナリ全体で10の尾に対して0.00248908996582を要しました。バージョン2は、ディクショナリ全体で10の尾に対して1.2963051796を要しました。私はほとんど投票しませんでした。
S.Lott、2008

「別の改行文字では機能しません。」必要に応じて、datacount( '\ n')をlen(data.splitlines())に置き換えます。
S.Lott、2008
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.