从大日志文件的某个偏移位置开始读取若干行

操作大日志文件时,经常需要从日志文件的上次处理到的位置开始,读取若干行进行处理,并记下当前位置以便下一次处理。同时,日志文件可能仍在变化中,新的记录可能正在不时地追加到文件末尾。

如果要处理的日志文件是一个静态的文件,不会再有内容追加进来,并且也不需要记下当前处理到的位置,那么代码很简单(以下代码都是 Python 代码):

f = open("bigfile.log", "rb")
for ln in f:
	deal(ln)

f.close()

如果同时要记下当前处理到的文件偏移(位置),这种方法就有一点问题了,比如:

f = open("bigfile.log", "rb")
f.seek(10000) # 从位置 10000 的地方开始处理
for ln in f:
	deal(ln)
	print f.tell() # 注意这个值

f.close()

在我的测试中,上面的代码中的 f.tell() 返回的始终是 18192,显然,这个数字是 seek 指定的初始偏移 10000 加上默认的 bufsize 8192 的值。搜索了一下,大致的解释是这样:文件对象 f 作为迭代器时,它会根据 bufsize 的值(例如 8k 或 10k)一次性读入一大块内容,再每次返回这一大块内容中的一行。既然已经读入了一大块内容,文件的指针当然已经移到那一大块内容结束的地方,所以每次循环时 f.tell() 的值都是这个数字。当然,如果文件很大,这一大块内容处理完后后面还有内容,程序就会再往后读一大块内容,对应地,f.tell() 将返回一个新的值。

这样的文件偏移显然不符合我们的需求。为了能得到准确的文件偏移,代码要改成这样:

f = open("bigfile.log", "rb")
f.seek(10000) # 从位置 10000 的地方开始处理
while True:
	ln = f.readline()
	if ln == "":
		break
	print f.tell() # 注意这个值

f.close()

使用这样的方式,f.tell() 就能返回准确的文件偏移了。

再把它稍微包装一下,写成一个迭代器,如下:

def yieldLine(log_fn, start=0):
	u"迭代器,返回 log_fn 从 start 位置开始的下一行及新的文件偏移"

	if not os.path.isfile(log_fn):
		raise StopIteration

	f = open(log_fn, "rb")
	f.seek(start)

	while True:

		ln = f.readline()
		if ln == "":
			f.close()
			raise StopIteration

		yield ln, f.tell()

# 用法:
yl = yieldLine("bigfile.log", start=1000)
for ln, pos in yl:
	deal(ln)

print pos

当然,如果在读文件的同时文件末尾也在不断地追加,最后一次读取时有可能会只读到半行内容。这时,需要判断一下读取的内容是否完整,如果不完整就先缓存起来,和下次读到的内容合并之后再处理。

分类:编程标签:Python

相关文章:

评论:

@拉风_zhang

hi oldj...

  1. 当脚本中断,之后再次运行,要从上次的位置继续去读,这个offset记录到哪里呢?必须要在磁盘上生成个文件记录吗?
  2. “需要判断一下读取的内容是否完整” 如何判断每一次读取是否完整呢?每一行都大小都不统一,用什么标准呢?

谢谢!(Python新手刚开始学习:P)

oldj

1:根据你的需要啊,如果你的脚本会先退出,将来再次启动时继续执行,那就记录到磁盘上,如果你的脚本一直在运行,只是会sleep一段时间,那直接记在某个内存变量中就可以了。

2:这个需要根据你的文件格式来判断,比如如果你的文件是日志类型的,每一行都可以用“t”分割为9列,并且最后一列能匹配某个正则,那么你就可以测试一下你读到的最后一行是否满足这个条件。当然,如果你的文件更新很快,你也可以总是把最后一行缓存起来,留到下一次读取时与后面的内容一起处理。

发表评论: