Obtenez les N dernières lignes d'un fichier avec Python, similaire à tail
j'écris un visualiseur de fichier journal pour une application web et pour cela je veux paginer à travers les lignes du fichier journal. Les éléments dans le fichier sont en ligne avec la nouvelle rubrique sur le fond.
donc j'ai besoin d'une méthode tail()
qui peut lire n
lignes du bas et soutient un décalage. Ce que j'ai inventé ressemble à ceci:
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
est-ce une approche raisonnable? Quelle est la méthode recommandée pour la queue du journal les fichiers avec des décalages?
28 réponses
c'est peut-être plus rapide que le vôtre. Ne pose aucune hypothèse quant à la longueur de la ligne. Sauvegarde dans le fichier, un bloc à la fois jusqu'à ce qu'il trouve le bon nombre de '\n' personnages.
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:])
Je n'aime pas les suppositions délicates sur la longueur des lignes quand -- en pratique -- on ne peut jamais savoir des choses comme ça.
Généralement, cela localisera les 20 dernières lignes sur le premier ou le second passage à travers la boucle. Si votre truc de 74 caractères est en fait précis, vous faites la taille du bloc 2048 et vous suivrez 20 lignes presque immédiatement.
en outre, Je ne brûle pas beaucoup de calories du cerveau en essayant de finesse l'alignement avec les blocs physiques OS. En utilisant ces paquets d'e/s de haut niveau, je doute que vous voyiez des conséquences sur les performances en essayant de vous aligner sur les limites des blocs du système D'exploitation. Si vous utilisez des entrées/sorties de niveau inférieur, alors vous pourriez voir une accélération.
suppose un système de type unix sur Python 2 que vous pouvez faire:
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]
pour python 3 vous pouvez faire:
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]
si la lecture de l'ensemble du fichier est acceptable, utilisez une deque.
from collections import deque
deque(f, maxlen=n)
avant 2.6, deques n'avait pas d'option maxlen, mais c'est assez facile à implémenter.
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
S'il s'agit d'une exigence de lire le fichier à la fin, alors utilisez un galop (A. K. a exponential) search.
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:]
la réponse de S. Lott ci-dessus fonctionne presque pour moi mais finit par me donner des lignes partielles. Il s'avère qu'il corrompt les données sur les limites des blocs parce que les données tiennent les blocs lus dans l'ordre inverse. Lorsque.'' join(données) est appelée, les blocs sont dans le mauvais ordre. Cette correctifs.
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)
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:]
Voici ma réponse. Pur python. À l'aide de timeit il semble assez rapide. Filing 100 lignes d'un fichier log qui a 100 000 lignes:
>>> 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
voici le code:
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:]
le code que j'ai fini par utiliser. Je pense que c'est le meilleur jusqu'à présent:
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
solution Simple et rapide avec 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()
j'ai trouvé le Popen ci-dessus pour être la meilleure solution. C'est rapide et sale, et il fonctionne Pour python 2.6 sur Unix machine j'ai utilisé le suivant
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
le contenu du fichier contient les dernières lignes du code. pour itérer la ligne de production par la ligne faire:
for line in GetLastNLines(50,'myfile.log').split('\n'):
print line
une version compatible python3 encore plus propre qui n'insère pas mais ajoute et inverse:
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:])
utilisez - le comme ceci:
with open(path, 'rb') as f:
last_lines = tail(f, 3).decode('utf-8')
Poster une réponse à la demande des intervenants sur ma réponse à une question similaire où la même technique a été utilisée pour muter la dernière ligne d'un fichier, ne pas l'obtenir.
pour un fichier de taille significative, mmap
c'est la meilleure façon de le faire. Pour améliorer la réponse existante mmap
, cette version est portable entre Windows et Linux, et devrait fonctionner plus rapidement (bien qu'il ne fonctionnera pas sans certains modifications sur Python 32 bits avec des fichiers dans la gamme GB, voir la autre réponse pour des conseils sur la manipulation de ceci, et pour modifier pour travailler sur 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)
cela suppose que le nombre de lignes suivies est suffisamment petit pour que vous puissiez les lire en toute sécurité dans la mémoire à la fois; vous pouvez également en faire une fonction de générateur et lire manuellement une ligne à la fois en remplaçant la ligne finale par:
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)
enfin, ce lire en mode binaire (nécessaire pour utiliser mmap
) de sorte qu'il donne str
lignes (Py2) et bytes
lignes (Py3); si vous voulez unicode
(Py2) ou str
(Py3), l'approche itérative pourrait être modifié pour décoder pour vous et / ou fixer des nouvelles lignes:
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
Note: j'ai tapé tout cela sur une machine où je n'ai pas accès à Python pour tester. S'il vous plaît, faites-moi savoir si j'ai tapé quoi que ce soit; c'était assez similaire à mon autre réponse que je penser il devrait fonctionner, mais les réglages (par exemple, la manipulation d'un offset
) pourrait conduire à de subtiles erreurs. Veuillez me faire savoir s'il y a des erreurs dans les commentaires.
basé sur la meilleure réponse de S. Lott (Sep 25 '08 à 21:43), mais corrigé pour les petits fichiers.
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
J'espère que c'est utile.
il existe quelques implémentations de tail sur pypi que vous pouvez installer en utilisant pip:
- mtFileUtil
- multitail
- log4tailer
- ...
selon votre situation, il peut y avoir des avantages à utiliser l'un de ces outils existants.
Voici une implémentation assez simple:
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()
pour l'efficacité avec des fichiers très volumineux (courant dans les situations de fichier journal où vous pouvez vouloir utiliser tail), vous voulez généralement éviter de lire l'ensemble du fichier (même si vous le faites sans lire l'ensemble du fichier en mémoire à la fois) cependant, vous avez besoin d'une façon ou d'une autre de travailler le décalage en lignes plutôt que des caractères. Une possibilité est de lire à l'envers avec seek () char par char, mais c'est très lent. Au lieu de cela, il est préférable de traiter dans des blocs plus grands.
j'ai un fonction utilitaire j'ai écrit il y a un certain temps pour lire des fichiers à l'envers qui peuvent être utilisés ici.
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
[Edit] a ajouté une version plus spécifique (évite d'avoir à revenir en arrière deux fois)
vous pouvez aller à la fin de votre fichier avec F. chercher (0, 2) et ensuite lire les lignes une à une avec le remplacement suivant pour readline ():
def readline_backwards(self, f):
backline = ''
last = ''
while not last == '\n':
backline = last + backline
if f.tell() <= 0:
return backline
f.seek(-1, 1)
last = f.read(1)
f.seek(-1, 1)
backline = last
last = ''
while not last == '\n':
backline = last + backline
if f.tell() <= 0:
return backline
f.seek(-1, 1)
last = f.read(1)
f.seek(-1, 1)
f.seek(1, 1)
return backline
basé sur la réponse D'Eyecue (Jun 10 '10 à 21:28): Cette classe ajoute la méthode head() et tail() pour classer l'objet.
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:]
Utilisation:
f = File('path/to/file', 'r')
f.head(3)
f.tail(3)
plusieurs de ces solutions ont des problèmes si le fichier ne se termine pas dans \n ou en s'assurant que la première ligne complète est lue.
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
mise à Jour @papercrane solution pour python3.
Ouvrir le fichier avec open(filename, 'rb')
et:
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:]
j'ai dû lire une valeur spécifique de la dernière ligne d'un fichier, et je suis tombé sur ce fil. Plutôt que de réinventer la roue en Python, j'ai fini avec un petit script shell, /usr / local / bin/get_last_netp:
#! /bin/bash
tail -n1 /home/leif/projects/transfer/export.log | awk {'print '}
et dans le programme Python:
from subprocess import check_output
last_netp = int(check_output("/usr/local/bin/get_last_netp"))
pas le premier exemple utilisant une deque, mais un plus simple. Celui-ci est général: il fonctionne sur n'importe quel objet itérable, pas seulement un fichier.
#!/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)
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
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
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]))
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)
j'ai trouvé un moyen probablement le plus facile de trouver le premier ou le dernier N Lignes d'un fichier""
dernières lignes D'un fichier(pour Ex:N=10)
file=open("xyz.txt",'r")
liner=file.readlines()
for ran in range((len(liner)-N),len(liner)):
print liner[ran]
premières lignes D'un fichier (pour Ex:N=10)
file=open("xyz.txt",'r")
liner=file.readlines()
for ran in range(0,N+1):
print liner[ran]
en y repensant, c'est probablement aussi rapide que n'importe quoi ici.
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]
C'est beaucoup plus simple. Et il semble se déchirer à un bon rythme.
c'est si simple:
def tail(fname,nl):
with open(fname) as f:
data=f.readlines() #readlines return a list
print(''.join(data[-nl:]))
bien que ce ne soit pas vraiment du côté efficace avec les gros fichiers, ce code est assez simple:
- Il lit le fichier d'objet,
f
. - il divise la chaîne retournée en utilisant newlines,
\n
. -
il obtient le tableau listes derniers index, en utilisant le signe négatif pour représenter les derniers index, et le
:
pour obtenir un subarray.
def tail(f,n): return "\n".join(f.read().split("\n")[-n:])