我有一个文件,我不知道它会有多大(它可能很大,但尺寸会有很大差异)。我想搜索最后10行左右,看看是否有任何一个字符串匹配。我需要尽可能快速有效地做到这一点,并且想知道是否有更好的东西:
s = "foo"
last_bit = fileObj.readlines()[-10:]
for line in last_bit:
if line == s:
print "FOUND"
答案 0 :(得分:34)
这是一个像MizardX一样的答案,但是没有明显的问题,即在最坏的情况下采用二次时间来重新扫描工作字符串,因为添加了块的新行。
与activestate解决方案(也似乎是二次方)相比,给定一个空文件不会爆炸,并且每个块读取而不是两个。
与产卵'尾巴'相比,这是独立的。 (但如果你拥有它,'尾巴'是最好的。)
与从末端抓取几个KB并希望它足够相比,这适用于任何行长度。
import os
def reversed_lines(file):
"Generate the lines of file in reverse order."
part = ''
for block in reversed_blocks(file):
for c in reversed(block):
if c == '\n' and part:
yield part[::-1]
part = ''
part += c
if part: yield part[::-1]
def reversed_blocks(file, blocksize=4096):
"Generate blocks of file's contents in reverse order."
file.seek(0, os.SEEK_END)
here = file.tell()
while 0 < here:
delta = min(blocksize, here)
here -= delta
file.seek(here, os.SEEK_SET)
yield file.read(delta)
按要求使用它:
from itertools import islice
def check_last_10_lines(file, key):
for line in islice(reversed_lines(file), 10):
if line.rstrip('\n') == key:
print 'FOUND'
break
编辑:将head()更改为head()中的itertools.imap()。 修改2:简化了revers_blocks()。 编辑3:避免重新扫描新行的尾部。 编辑4:重写了reversed_lines()因为str.splitlines()忽略了一个'\ n',正如BrianB注意到的那样(谢谢)。
请注意,在非常旧的Python版本中,循环中的字符串连接将采用二次时间。 CPython至少在过去几年中自动避免了这个问题。
答案 1 :(得分:33)
# Tail
from __future__ import with_statement
find_str = "FIREFOX" # String to find
fname = "g:/autoIt/ActiveWin.log_2" # File to check
with open(fname, "r") as f:
f.seek (0, 2) # Seek @ EOF
fsize = f.tell() # Get Size
f.seek (max (fsize-1024, 0), 0) # Set pos @ last n chars
lines = f.readlines() # Read to end
lines = lines[-10:] # Get last 10 lines
# This returns True if any line is exactly find_str + "\n"
print find_str + "\n" in lines
# If you're searching for a substring
for line in lines:
if find_str in line:
print True
break
答案 2 :(得分:8)
如果您在POSIX系统上运行Python,可以使用'tail -10'来检索最后几行。这可能比编写自己的Python代码以获得最后10行更快。而不是直接打开文件,从命令'tail -10 filename'打开一个管道。如果您确定日志输出(例如,您知道从不任何长数百或数千个字符的长行),那么使用“读取最后2KB”之一列出的方法没问题。
答案 3 :(得分:7)
我认为读取文件的最后2 KB左右应该确保你获得10行,并且不应该过多地占用资源。
file_handle = open("somefile")
file_size = file_handle.tell()
file_handle.seek(max(file_size - 2*1024, 0))
# this will get rid of trailing newlines, unlike readlines()
last_10 = file_handle.read().splitlines()[-10:]
assert len(last_10) == 10, "Only read %d lines" % len(last_10)
答案 4 :(得分:5)
这是使用mmap
的版本,看起来非常有效。最重要的是mmap
将自动为您处理文件到内存的分页要求。
import os
from mmap import mmap
def lastn(filename, n):
# open the file and mmap it
f = open(filename, 'r+')
m = mmap(f.fileno(), os.path.getsize(f.name))
nlcount = 0
i = m.size() - 1
if m[i] == '\n': n += 1
while nlcount < n and i > 0:
if m[i] == '\n': nlcount += 1
i -= 1
if i > 0: i += 2
return m[i:].splitlines()
target = "target string"
print [l for l in lastn('somefile', 10) if l == target]
答案 5 :(得分:2)
我想我记得在我必须做类似的事情时调整this blog post from Manu Garg的代码。
答案 6 :(得分:2)
如果您使用的是unix框,os.popen("tail -10 " + filepath).readlines()
可能是最快的方法。否则,它取决于你想要的强大程度。到目前为止提出的方法都会以这种或那种方式落空。对于最常见情况下的稳健性和速度,您可能需要像对数搜索这样的东西:使用file.seek转到文件末尾减去1000个字符,读入它,检查它包含多少行,然后到EOF减去3000个字符,读取2000个字符,计算行数,然后EOF减去7000,读取4000个字符,计算行数等,直到您拥有所需的行数。但是如果你确定它总是会在具有合理线长的文件上运行,那么你可能不需要它。
您可能还会在source code中找到unix tail
命令的一些灵感。
答案 7 :(得分:2)
我遇到了这个问题,解析了LARGE系统日志文件的最后一小时,并在activestate的配方网站中使用了这个函数......(http://code.activestate.com/recipes/439045/)
!/usr/bin/env python
# -*-mode: python; coding: iso-8859-1 -*-
#
# Copyright (c) Peter Astrand <astrand@cendio.se>
import os
import string
class BackwardsReader:
"""Read a file line by line, backwards"""
BLKSIZE = 4096
def readline(self):
while 1:
newline_pos = string.rfind(self.buf, "\n")
pos = self.file.tell()
if newline_pos != -1:
# Found a newline
line = self.buf[newline_pos+1:]
self.buf = self.buf[:newline_pos]
if pos != 0 or newline_pos != 0 or self.trailing_newline:
line += "\n"
return line
else:
if pos == 0:
# Start-of-file
return ""
else:
# Need to fill buffer
toread = min(self.BLKSIZE, pos)
self.file.seek(-toread, 1)
self.buf = self.file.read(toread) + self.buf
self.file.seek(-toread, 1)
if pos - toread == 0:
self.buf = "\n" + self.buf
def __init__(self, file):
self.file = file
self.buf = ""
self.file.seek(-1, 2)
self.trailing_newline = 0
lastchar = self.file.read(1)
if lastchar == "\n":
self.trailing_newline = 1
self.file.seek(-1, 2)
# Example usage
br = BackwardsReader(open('bar'))
while 1:
line = br.readline()
if not line:
break
print repr(line)
它工作得非常好,比fileObj.readlines()[ - 10:]更有效率,这使得python将整个文件读入内存,然后将最后十行从中删除。
答案 8 :(得分:1)
您可以从文件末尾读取大约1,000个字节的块到缓冲区,直到您有10行。
答案 9 :(得分:1)
我接受了mhawke建议使用mmap
并写了一个使用rfind
的版本:
from mmap import mmap
import sys
def reverse_file(f):
mm = mmap(f.fileno(), 0)
nl = mm.size() - 1
prev_nl = mm.size()
while nl > -1:
nl = mm.rfind('\n', 0, nl)
yield mm[nl + 1:prev_nl]
prev_nl = nl + 1
def main():
# Example usage
with open('test.txt', 'r+') as infile:
for line in reverse_file(infile):
sys.stdout.write(line)
答案 10 :(得分:1)
您还可以在反转文件时计算行数,而不是猜测字节偏移量。
lines = 0
chunk_size = 1024
f = file('filename')
f.seek(0, 2)
f.seek(f.tell() - chunk_size)
while True:
s = f.read(chunk_size)
lines += s.count('\n')
if lines > NUM_OF_LINES:
break
f.seek(f.tell() - chunk_size*2)
现在该文件可以运行readlines()
。您还可以缓存第一次读取的字符串,以消除两次读取文件的相同部分。
答案 11 :(得分:0)
首先,返回一个列表的函数:
def lastNLines(file, N=10, chunksize=1024):
lines = None
file.seek(0,2) # go to eof
size = file.tell()
for pos in xrange(chunksize,size-1,chunksize):
# read a chunk
file.seek(pos,2)
chunk = file.read(chunksize)
if lines is None:
# first time
lines = chunk.splitlines()
else:
# other times, update the 'first' line with
# the new data, and re-split
lines[0:1] = (chunk + lines[0]).splitlines()
if len(lines) > N:
return lines[-N:]
file.seek(0)
chunk = file.read(size-pos)
lines[0:1] = (chunk + lines[0]).splitlines()
return lines[-N:]
其次,一个以相反的顺序迭代行的函数:
def iter_lines_reversed(file, chunksize=1024):
file.seek(0,2)
size = file.tell()
last_line = ""
for pos in xrange(chunksize,size-1,chunksize):
# read a chunk
file.seek(pos,2)
chunk = file.read(chunksize) + last_line
# split into lines
lines = chunk.splitlines()
last_line = lines[0]
# iterate in reverse order
for index,line in enumerate(reversed(lines)):
if index > 0:
yield line
# handle the remaining data at the beginning of the file
file.seek(0)
chunk = file.read(size-pos) + last_line
lines = chunk.splitlines()
for line in reversed(lines):
yield line
对于你的例子:
s = "foo"
for index, line in enumerate(iter_lines_reversed(fileObj)):
if line == s:
print "FOUND"
break
elif index+1 >= 10:
break
修改:现在自动获取文件大小 Edit2:现在只迭代10行。
答案 12 :(得分:0)
此解决方案只会读取一次文件,但使用2个文件对象指针可以获取最后N行文件而无需重新读取它:
def getLastLines (path, n):
# return the las N lines from the file indicated in path
fp = open(path)
for i in range(n):
line = fp.readline()
if line == '':
return []
back = open(path)
for each in fp:
back.readline()
result = []
for line in back:
result.append(line[:-1])
return result
s = "foo"
last_bit = getLastLines(r'C:\Documents and Settings\ricardo.m.reyes\My Documents\desarrollo\tail.py', 10)
for line in last_bit:
if line == s:
print "FOUND"
答案 13 :(得分:0)
就个人而言,我很想突破shell并调用tail -n10来加载文件。但后来我不是一个真正的Python程序员;)
答案 14 :(得分:0)
也许这可能有用:
import os.path
path = 'path_to_file'
os.system('tail -n1 ' + path)
答案 15 :(得分:0)
读取文件的最后几个Ks,并将其拆分为行,仅返回最后10个。
该块的开始不太可能落在线边界上,但无论如何你都会放弃第一行。
答案 16 :(得分:0)
感谢18个Darius Bacon提出的解决方案,但实现速度提高了30%,并包装到io.BaseIO类中。
class ReverseFile(io.IOBase):
def __init__ (self, filename, headers=1):
self.fp = open(filename)
self.headers = headers
self.reverse = self.reversed_lines()
self.end_position = -1
self.current_position = -1
def readline(self, size=-1):
if self.headers > 0:
self.headers -= 1
raw = self.fp.readline(size)
self.end_position = self.fp.tell()
return raw
raw = next(self.reverse)
if self.current_position > self.end_position:
return raw
raise StopIteration
def reversed_lines(self):
"""Generate the lines of file in reverse order.
"""
part = ''
for block in self.reversed_blocks():
block = block + part
block = block.split('\n')
block.reverse()
part = block.pop()
if block[0] == '':
block.pop(0)
for line in block:
yield line + '\n'
if part:
yield part
def reversed_blocks(self, blocksize=0xFFFF):
"Generate blocks of file's contents in reverse order."
file = self.fp
file.seek(0, os.SEEK_END)
here = file.tell()
while 0 < here:
delta = min(blocksize, here)
here -= delta
file.seek(here, os.SEEK_SET)
self.current_position = file.tell()
yield file.read(delta)
一个例子
rev = ReverseFile(filename)
for i, line in enumerate(rev):
print("{0}: {1}".format(i, line.strip()))