पूंछ के समान पायथन के साथ एक फ़ाइल की अंतिम एन लाइनें प्राप्त करें


181

मैं एक वेब एप्लिकेशन के लिए लॉग फाइल व्यूअर लिख रहा हूं और इसके लिए मैं लॉग फाइल की तर्ज पर पेजेट करना चाहता हूं। फ़ाइल में आइटम नीचे की ओर नवीनतम आइटम के साथ पंक्तिबद्ध हैं।

इसलिए मुझे एक ऐसी 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जाना चाहिए
इगोर फ़ोबिया

जवाबों:


123

यह आपके मुकाबले जल्दी हो सकता है। रेखा की लंबाई के बारे में कोई धारणा नहीं बनाता है। फ़ाइल एक ब्लॉक के माध्यम से एक समय में वापस आ जाती है जब तक कि उसे '\ n' वर्णों की सही संख्या नहीं मिल जाती।

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

    BLOCK_SIZE = 1024
    f.seek(0, 2)
    block_end_byte = f.tell()
    lines_to_go = total_lines_wanted
    block_number = -1
    blocks = [] # blocks of size BLOCK_SIZE, in reverse order starting
                # from the end of the file
    while lines_to_go > 0 and block_end_byte > 0:
        if (block_end_byte - BLOCK_SIZE > 0):
            # read the last block we haven't yet read
            f.seek(block_number*BLOCK_SIZE, 2)
            blocks.append(f.read(BLOCK_SIZE))
        else:
            # file too small, start from begining
            f.seek(0,0)
            # only read what was not read
            blocks.append(f.read(block_end_byte))
        lines_found = blocks[-1].count('\n')
        lines_to_go -= lines_found
        block_end_byte -= BLOCK_SIZE
        block_number -= 1
    all_read_text = ''.join(reversed(blocks))
    return '\n'.join(all_read_text.splitlines()[-total_lines_wanted:])

जब मैं एक व्यावहारिक बात के रूप में - लाइन की लंबाई के बारे में मुश्किल धारणाएँ पसंद नहीं करता - तो आप उस तरह की चीजों को कभी नहीं जान सकते।

आम तौर पर, यह लूप के माध्यम से पहले या दूसरे पास पर अंतिम 20 पंक्तियों का पता लगाएगा। यदि आपकी 74 चरित्र वाली वस्तु वास्तव में सटीक है, तो आप ब्लॉक का आकार 2048 बनाते हैं और आप 20 पंक्तियों को लगभग तुरंत हटा देंगे।

इसके अलावा, मैं शारीरिक ओएस ब्लॉकों के साथ चालाकी संरेखण करने की कोशिश कर रहे मस्तिष्क कैलोरी का एक बहुत जला नहीं है। इन उच्च-स्तरीय I / O पैकेजों का उपयोग करते हुए, मुझे संदेह है कि आप OS ब्लॉक सीमाओं पर संरेखित करने का कोई भी प्रदर्शन परिणाम देखेंगे। यदि आप निम्न-स्तर I / O का उपयोग करते हैं, तो आप एक स्पीडअप देख सकते हैं।


अपडेट करें

पायथन 3.2 और ऊपर के लिए, बाइट्स पर प्रक्रिया का पालन करें। पाठ फ़ाइलों के रूप में (जो मोड स्ट्रिंग में "बी" के बिना खोले गए हैं ), केवल फ़ाइल की शुरुआत के सापेक्ष ही अनुमति दी जाती है (अपवाद बहुत फ़ाइल अंत की मांग की जा रही है) तलाश के साथ (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 (ब्लॉक * 1024, 2)
ohnoes

1
वास्तव में बहुत अच्छा दृष्टिकोण। मैं ऊपर कोड का एक थोड़ा संशोधित संस्करण का उपयोग किया और यह नुस्खा के साथ आया था: code.activestate.com/recipes/577968-log-watcher-tail-f-log
जैमपाओलो Rodolà

6
अब अजगर 3.2 में काम नहीं करता है। मुझे मिल io.UnsupportedOperation: can't do nonzero end-relative seeksरहा है मैं ऑफसेट को 0 में बदल सकता हूं, लेकिन यह फ़ंक्शन के उद्देश्य को हरा देता है।
तार्किक पतन

4
@DavidEnglund कारण यहाँ है । संक्षेप में: फ़ाइल मोड के सापेक्ष मांग करने की अनुमति पाठ मोड में नहीं है, संभवत: क्योंकि फ़ाइल सामग्री को डीकोड किया जाना है, और, सामान्य रूप से, एन्कोडेड बाइट्स के अनुक्रम के भीतर एक मनमाना स्थिति की मांग करने पर आप अपरिभाषित हो सकते हैं। उस स्थिति से शुरू होने वाले यूनिकोड को डिकोड करने का प्रयास। लिंक पर दिया गया सुझाव द्विआधारी मोड में फ़ाइल खोलने की कोशिश करना है और डिकोडरर अपवादों को पकड़ते हुए, स्वयं डिकोडिंग करना है।
अधिकतम

6
इस कोड का उपयोग न करें। यह अजगर 2.7 में कुछ सीमावर्ती मामलों में लाइनों को दूषित करता है। नीचे @papercrane से उत्तर इसे ठीक करता है।
xApple

88

अजगर 2 पर एक यूनिक्स जैसी प्रणाली मानता है जो आप कर सकते हैं:

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]

अजगर 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 एक फाइल है जो ऑब्जेक्ट की तरह है।
आर्मिन रौनाकर

40
प्लेटफ़ॉर्म निर्भरता अस्वीकार्य है यह सवाल नहीं है। मैं यह देखने में विफल रहता हूं कि यह दो डाउनवॉट का हकदार क्यों है जब यह एक बहुत ही यूनिक्स प्रदान करता है (हो सकता है कि आप जो खोज रहे हैं ... निश्चित रूप से मेरे लिए था) बिल्कुल वैसा ही करने का तरीका जो सवाल पूछता है।
जर्बरीत

3
धन्यवाद, मैं सोच रहा था कि मुझे इसे शुद्ध पायथन में हल करना था लेकिन जब वे हाथ में होते हैं तो यूनिक्स की उपयोगिताओं का उपयोग नहीं करने का कोई कारण नहीं है, इसलिए मैं इसके साथ गया। आधुनिक पायथन में एफडब्ल्यूआईडब्ल्यू, सबप्रोसेस.चेक_आउटपुट की संभावना है। यह चीजों को थोड़ा सरल करता है क्योंकि यह आउटपुट को एक स्ट्रिंग के रूप में लौटाता है, और एक गैर-शून्य निकास कोड पर उठता है।
मैरोनी

3
हालाँकि यह प्लेटफ़ॉर्म डिपेंडेंट है, लेकिन यह करने का एक बहुत ही कुशल तरीका है, जो पूछा गया है, साथ ही इसे करने का एक बहुत तेज़ तरीका है (आपको पूरी फ़ाइल को मेमोरी में लोड नहीं करना है)। @ शब्बीरदे
अर्थमीयन

6
आप offset_total = str(n+offset)इस तरह की भरपाई करना चाहते हैं: और stdin,stdout = os.popen2("tail -n "+offset_total+" "+f)बचने के लिए इस पंक्ति को बदलेंTypeErrors (cannot concatenate int+str)
AddColor

32

यहाँ मेरा जवाब है। शुद्ध अजगर। समय का उपयोग करते हुए यह बहुत तेज लगता है। लॉग फ़ाइल की 100 पंक्तियाँ जिसमें 100,000 पंक्तियाँ हैं:

>>> 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हालत उसे पकड़ नहीं पाएगी?
मैक्सिमिलियन पीटर्स

मेरी समझ के लिए एक प्रश्न: os.SEEK_ENDकेवल स्पष्टता के लिए उपयोग किया जाता है? जहां तक ​​मैंने पाया है, इसका मूल्य स्थिर (= 2) है। मैं इसे छोड़ने के लिए सक्षम होने के बारे में सोच रहा था import os। महान समाधान के लिए धन्यवाद!
n1k31t4 10

2
@MaximilianPeters हाँ। यह आवश्यक नहीं है। मैंने इसे टिप्पणी की।
ग्लेनबोट ऑक्ट

@DexterMorgan आप os.SEEK_ENDइसके पूर्णांक समकक्ष के साथ बदल सकते हैं । यह मुख्य रूप से पठनीयता के लिए था।
ग्लेनबोट

1
मैंने उत्थान किया, लेकिन एक छोटा सा नाइट है। तलाश के बाद, पहली पंक्ति पढ़ने अपूर्ण हो सकती है, इसलिए एन _complete_lines मैं बदल पाने के लिए while len(lines_found) < linesकरने के लिए while len(lines_found) <= linesमेरी कॉपी में। धन्यवाद!
ग्राहम क्लीं

30

यदि पूरी फ़ाइल पढ़ना स्वीकार्य है तो एक deque का उपयोग करें।

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

2.6 से पहले, देवताओं के पास एक अधिकतम विकल्प नहीं था, लेकिन इसे लागू करना काफी आसान है।

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

1
@ 2mac घातीय खोज । यह फ़ाइल के अंत से पुनरावृत्त रूप से पढ़ता है, प्रत्येक बार पढ़ी गई राशि को दोगुना करता है, जब तक कि पर्याप्त लाइनें नहीं मिल जाती हैं।
ए। कोदि

मुझे लगता है कि अंत से पढ़ने के लिए समाधान UTF-8 के साथ एन्कोडेड फ़ाइलों का समर्थन नहीं करेगा, क्योंकि चरित्र की लंबाई परिवर्तनशील है, और आप कुछ संभावित ऑफसेट पर भूमि (संभावना है) को सही ढंग से व्याख्या नहीं कर सकते हैं।
माइक

दुर्भाग्य से आपका सरपट खोज समाधान अजगर के लिए काम नहीं करता है 3. जैसा कि f.seek () नकारात्मक ऑफसेट नहीं लेता है। मैंने आपके कोड को अपडेट कर दिया है कि यह अजगर 3 लिंक के
इसका जूल

25

एस.लॉट का उत्तर मेरे ऊपर लगभग काम करता है लेकिन मुझे आंशिक रूप से समाप्त कर देता है। यह पता चला है कि यह ब्लॉक सीमाओं पर डेटा को दूषित करता है क्योंकि डेटा उल्टे क्रम में रीड ब्लॉक को रखता है। जब '' .join (डेटा) कहा जाता है, तो ब्लॉक गलत क्रम में हैं। यह ठीक करता है।

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 संरचना का उपयोग करें?
सेर्गेई 11 जी सेप

1
दुख की बात नहीं पायथन 3 संगत ... यह जानने की कोशिश कर रहा है।
शर्लक 70

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
सवाल का ठीक जवाब नहीं देता।
sheki

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पायथन स्तर पर एक समय पर बाइट की जांच करने के बजाय, न्यूक्लियर के लिए पीछे की ओर स्कैन करने के लिए विधि का उपयोग किया ; C अंतर्निहित कॉल आमतौर पर बहुत से जीतती है)। छोटे आदानों के लिए, एक के dequeसाथ maxlenसरल और शायद इसी तरह तेज है।
छाया रेंजर

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 साल पुराने सवाल का जवाब बहुत सारे जवाबों के साथ न जोड़ें। लेकिन मेरी मदद करें: आपके कोड में पायथन 3 के लिए क्या विशिष्ट है?
usr2564301

अन्य उत्तर वास्तव में बाहर काम नहीं कर रहे थे अच्छी तरह से :-) py3: देखने stackoverflow.com/questions/136168/...
Hauke Rehfeld

3

Python3 पर @papercrane समाधान अपडेट करें। के साथ फ़ाइल खोलें open(filename, 'rb')और:

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उत्तर पर सुधार करने के लिए , यह संस्करण विंडोज़ और लिनक्स के बीच पोर्टेबल है, और तेज़ी से चलना चाहिए (हालांकि यह जीबी रेंज में फ़ाइलों के साथ 32 बिट पायथन पर कुछ संशोधनों के बिना काम नहीं करेगा, इसे संभालने पर संकेत के लिए अन्य उत्तर देखें , और पायथन 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)

यह माना जाता है कि लाइनों की संख्या काफी छोटी है आप सुरक्षित रूप से उन सभी को एक बार में मेमोरी में पढ़ सकते हैं; आप इसे जनरेटर फ़ंक्शन भी बना सकते हैं और अंतिम पंक्ति को प्रतिस्थापित करके एक बार में मैन्युअल रूप से एक पंक्ति पढ़ सकते हैं:

        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

नोट: मैंने यह सब एक मशीन पर टाइप किया जहां मुझे परीक्षण करने के लिए पायथन की पहुंच की कमी है। कृपया मुझे बताएं कि क्या मैंने कुछ टाइप किया है; यह मेरे अन्य उत्तर के समान था कि मुझे लगता है कि इसे काम करना चाहिए, लेकिन ट्वीक्स (जैसे हैंडलिंग offset) सूक्ष्म त्रुटियों को जन्म दे सकता है। अगर कोई ग़लती हो तो कृपया मुझे टिप्पणियों में बताएं।


3

मैंने ऊपर दिए गए पोपेन को सबसे अच्छा समाधान पाया। यह त्वरित और गंदा है और यह यूनिक्स मशीन पर पायथन 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 लाइनें होंगी। दक्षिणपूर्वी लाइन के माध्यम से iterate तक लाइन द्वारा:

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

2

एस.लॉट के शीर्ष मतदान जवाब पर आधारित (21:43 पर 25 सितंबर), लेकिन छोटी फाइलों के लिए तय किया गया।

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

पिपी पर पूंछ के कुछ मौजूदा कार्यान्वयन हैं जिन्हें आप पाइप का उपयोग करके स्थापित कर सकते हैं:

  • mtFileUtil
  • multitail
  • log4tailer
  • ...

आपकी स्थिति के आधार पर, इन मौजूदा उपकरणों में से किसी एक का उपयोग करने के फायदे हो सकते हैं।


क्या आप किसी भी मॉड्यूल के बारे में जानते हैं जो विंडोज पर काम करता है? मैंने कोशिश की tailhead, tailerलेकिन उन्होंने काम नहीं किया। भी आजमाया mtFileUtil। यह शुरू में त्रुटि फेंक रहा था क्योंकि printबयान में कोष्ठक नहीं था (मैं पायथन 3.6 पर हूं)। मैंने उन्हें जोड़ा reverse.pyऔर त्रुटि संदेश चला गया था, लेकिन जब मेरी स्क्रिप्ट मॉड्यूल ( mtFileUtil.tail(open(logfile_path), 5)) को कॉल करती है , तो यह कुछ भी प्रिंट नहीं करता है।
टेक्नेक्स्ट


1

बहुत बड़ी फ़ाइलों के साथ दक्षता के लिए (लॉगफ़ाइल स्थितियों में सामान्य जहां आप पूंछ का उपयोग करना चाह सकते हैं), आप आम तौर पर पूरी फ़ाइल को पढ़ने से बचना चाहते हैं (भले ही आप इसे पूरी फ़ाइल को एक बार में पढ़ने के बिना करते हैं), फिर भी, आप करते हैं किसी तरह से वर्णों के बजाय लाइनों में ऑफसेट काम करने की जरूरत है। एक संभावना के साथ पीछे की ओर पढ़ रहा है () char by char, लेकिन यह बहुत धीमी है। इसके बजाय, बड़े ब्लॉकों में प्रक्रिया करना बेहतर है।

मेरे पास एक उपयोगिता फ़ंक्शन है जिसे मैंने कुछ समय पहले फ़ाइलों को पीछे की ओर पढ़ने के लिए लिखा था जो यहां उपयोग किया जा सकता है।

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

[संपादित करें] और अधिक विशिष्ट संस्करण जोड़े गए (दो बार रिवर्स करने की आवश्यकता से बचा जाता है)


एक त्वरित परीक्षण से पता चलता है कि यह ऊपर से मेरे संस्करण की तुलना में बहुत खराब प्रदर्शन करता है। शायद आपकी बफरिंग की वजह से।
Armin Ronacher

मुझे संदेह है कि मैं कई बार पीछे की ओर देख रहा हूं, इसलिए रीडहेड बफर का अच्छा उपयोग नहीं हो रहा है। हालाँकि, मुझे लगता है कि यह तब बेहतर हो सकता है जब लाइन की लंबाई पर आपका अनुमान सटीक न हो (जैसे कि बहुत बड़ी लाइनें), क्योंकि यह इस मामले में डेटा को दोबारा पढ़ने से बचता है।
ब्रायन

1

आप f.seek (0, 2) के साथ अपनी फ़ाइल के अंत में जा सकते हैं और फिर रीडलाइन के लिए निम्नलिखित प्रतिस्थापन के साथ एक-एक करके लाइनों को पढ़ सकते हैं ():

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

आईक्यू के उत्तर के आधार पर (जून 10 '10 21:28 पर): यह वर्ग ऑब्जेक्ट को दर्ज करने के लिए सिर () और पूंछ () विधि जोड़ता है।

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

महान उदाहरण! इससे पहले कि आप कोशिश के उपयोग की व्याख्या कर सकें f.seek? इससे पहले क्यों नहीं with open? इसके अलावा, exceptतुम में क्यों एक f.readlines()??

ईमानदारी से, कोशिश शायद सबसे पहले होनी चाहिए .. मुझे याद नहीं है कि एक मानक मानक लिनक्स सिस्टम पर खुले () को छोड़कर (/ / / passwd को छोड़कर) हमेशा पकड़ने योग्य नहीं होना चाहिए। कोशिश करें, तो अधिक सामान्य क्रम है।
GL2014


1

एक और समाधान

अगर आपकी txt फाइल इस तरह दिखती है: माउस स्नेक कैट छिपकली भेड़िया कुत्ता

आप केवल अजगर '' में सरणी अनुक्रमण का उपयोग करके इस फ़ाइल को उल्टा कर सकते हैं

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

मुझे एक फ़ाइल की अंतिम पंक्ति से एक विशिष्ट मूल्य पढ़ना था, और इस थ्रेड पर ठोकर खाई। पायथन में पहिया को फिर से मजबूत करने के बजाय, मैंने एक छोटे शेल स्क्रिप्ट के साथ समाप्त किया, जिसे / usr / स्थानीय / बिन / get_last_netp के रूप में सहेजा गया:

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

और पायथन कार्यक्रम में:

from subprocess import check_output

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

0

एक छल का उपयोग करके पहला उदाहरण नहीं है, लेकिन एक सरल है। यह एक सामान्य बात है: यह किसी भी ऑब्जेक्ट पर काम करता है, न कि केवल एक फ़ाइल पर।

#!/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 द्वारा दिए गए उत्तर के लिए अद्यतन

अजगर 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]

यह बहुत सरल है। और यह एक अच्छी गति के साथ चीर करने लगता है।


क्योंकि यहाँ लगभग सबकुछ 30 एमबी से अधिक की लॉग फ़ाइलों के साथ काम नहीं करता है या इतनी ही मात्रा में मेमोरी को रैम में लोड किए बिना;) आपका पहला संस्करण बहुत बेहतर है, लेकिन यहाँ परीक्षण फ़ाइलों के लिए यह मेरी तुलना में थोड़ा खराब है। और यह विभिन्न न्यूलाइन वर्णों के साथ काम नहीं करता है।
आर्मिन रौनाकर

3
मैं गलत था। संस्करण 1 ने शब्दकोश के माध्यम से 10 पूंछों के लिए 0.00248908996582 लिया। संस्करण 2 ने शब्दकोश के माध्यम से 10 पूंछों के लिए 1.2963051796 लिया। मैं लगभग खुद को वोट करूंगा।
S.Lott

"विभिन्न न्यूलाइन वर्णों के साथ काम नहीं करता है।" यदि यह मायने रखता है, तो datacount ('\ n') को len (data.splitlines ()) से बदलें।
S.Lott
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.