python实现tail -f 功能


Posted in Python onJanuary 17, 2020

tailf与tail -f类似:当文件不增长时并不访问文件
tail -f:只跟踪文件内容
tail -F:文件内容与文件名都跟踪

这篇文章最初是因为reboot的群里,有人去面试,笔试题有这个题,不知道怎么做,什么思路,就发群里大家讨论

我想了一下,简单说一下我的想法吧,当然,也有很好用的pyinotify模块专门监听文件变化,不过我更想介绍的,是解决的思路,毕竟作为面试官,还是想看到一下解决问题的思路,而且我觉得这一题的难点不在于监控文件增量,而在于怎么打印最后面10行

希望大家读这篇文章前,对python基础、处理文件和常用模块有一个简单的了解,知道下面几个名词是啥

open('a.txt')
file.seek
file.tell
time.sleep()

下面思路限于我个人知识,免不了有错误和考虑不周的,希望大家有更好的方法提出来,我随时优化代码,题目的感觉没啥太多的坑,下面让天真烂漫的蜗牛教大家用python的思路

怎么用python实现

其实思路也不难啦

  • 打开这个文件,指针移到最后
  • 每隔一秒就尝试readline一下,有内容就打印出来,没内容就sleep
  • 大概就是这么个意思

监听文件

思路如下:

  • 用open打开文件
  • 用seek文件指针,给大爷把跳到文件最后面
  • while True进行循环

持续不停的readline,如果能读到内容,打印出来即可

代码呼之欲出

with open('test.txt') as f:
  f.seek(0,2)
  while True:
    last_pos = f.tell()
    line = f.readline()
    if line:
      print line

代码说明

  • seek第二个参数2,意思就是从文件结尾处开始seek,更标准的写法使用os模块下面的SEEK_END,可读性更好
  • 只写出了简单的逻辑,代码简单粗暴,如果这个题目是10分的话,最多也就拿4分吧,不能再多了

优化点

  • print有缺陷,每次都是新的一行,换成sys.stdout.write(line)更和谐
  • 文件名传参,不能写死
  • 直接打印可以当成默认行为,具体要做什么,可以写成函数处理,这样我们就可以把新行做其他处理,比如展示在浏览器里
  • 加上容错处理,比如文件不存在会报错
  • while True一直都文件,比较耗性能,每读一次,间隔一秒比较靠谱
  • 调用time.sleep(1)
  • 用类来组织代码

实例代码如下

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import time
class Tail():
  def __init__(self,file_name,callback=sys.stdout.write):
    self.file_name = file_name
    self.callback = callback
  def follow(self):
    try:
      with open(self.file_name) as f:
        f.seek(0,2)
        while True:
          line = f.readline()
          if line:
            self.callback(line)
          time.sleep(1)
    except Exception,e:
      print '打开文件失败,?澹?纯次募?遣皇遣淮嬖冢?蛘呷ㄏ抻形侍?
      print e

使用方法:

# 使用默认的sys.stdout.write打印到屏幕
py_tail = Tail('test.txt')
py_tail.follow()
# 自己定义处理函数
def test_tail(line):
  print 'xx'+line+'xx'
py_tail1 = Tail('test.txt', test_tail)
py_tail1.follow()

咦 等等,tail -f 默认还会打印最后10行,这个好像才是这个题目的难点所在,众所周知,python里读文件指针,只能移动到固定位置,不能判断是哪一行,?澹?仁迪旨虻サ模?鸾ゼ忧堪?/p>

默认打印最后10行

现在这个代码,大概能拿6分啦,我们还有一个功能没做,那就是打印最后n行,默认是10行,现在把这个功能加上,加一个函数就行啦

文件小的时候

我们知道,readlines可以获取所有内容,并且分行,代码呼之欲出,获取list最后10行很简单有么有,切片妥妥的

# 演示代码,说明核心逻辑,完整代码在下面
last_lines = f.readlines()[-10:]
for line in last_lines:
  self.callback(line)

此时代码变成这样了

import sys
import time
class Tail():
  def __init__(self,file_name,callback=sys.stdout.write):
    self.file_name = file_name
    self.callback = callback
  def follow(self,n=10):
    try:
      with open(self.file_name) as f:
        self._file = f
        self.showLastLine(n)
        self._file.seek(0,2)
        while True:
          line = self._file.readline()
          if line:
            self.callback(line)
    except Exception,e:
      print '打开文件失败,?澹?纯次募?遣皇遣淮嬖冢?蛘呷ㄏ抻形侍?
      print e
  def showLastLine(self, n):
    last_lines = self._file.readlines()[-10:]
    for line in last_lines:
      self.callback(line)

更进一步:大的日志怎么办

此时代码有7分时很随意啦,但是如果文件特别大呢,特别是日志文件,很容易几个G,我们只需要最后几行,全部读出来内存受不了,所以我们要继续优化showLastLine函数,我觉得这才是这题的难点所在

大概的思路如下

我先估计日志一行大概是100个字符,注意,我只是估计一个大概,多了也没关系,我们只是需要一个初始值,后面会修正

我要读十行,所以一开始就seek到离结尾1000的位置seek(-1000,2),把最后1000个字符读出来,然后有一个判断

如果这1000个字符长度大于文件长度,不用管了 属于级别1的情况,直接read然后split

如果1000个字符里面的换行符大于10,说明1000个字符里,大于10行,那也好办,处理思路类似级别1,直接split取后面十个

问题就在于 如果小于10怎么处理

比如1000个里,只有四个换行符,说明一行大概是1000/4=250个字符,我们还缺6行,所以我们再读250*5=1500,一共有2500的大概比较靠谱,然后在对2500个字符进行相同的逻辑判断,直到读出的字符里,换行符大于10,读取结束

逻辑清晰以后,代码就呼之欲出啦

加上注释的版本

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import sys
import time
class Tail():
  def __init__(self,file_name,callback=sys.stdout.write):
    self.file_name = file_name
    self.callback = callback
  def follow(self,n=10):
    try:
      # 打开文件
      with open(self.file_name) as f:
        self._file = f
        self._file.seek(0,2)
        # 存储文件的字符长度
        self.file_length = self._file.tell()
        # 打印最后10行
        self.showLastLine(n)
        # 持续读文件 打印增量
        while True:
          line = self._file.readline()
          if line:
            self.callback(line)
          time.sleep(1)
    except Exception,e:
      print '打开文件失败,?澹?纯次募?遣皇遣淮嬖冢?蛘呷ㄏ抻形侍?
      print e
  def showLastLine(self, n):
    # 一行大概100个吧 这个数改成1或者1000都行
    len_line = 100
    # n默认是10,也可以follow的参数传进来
    read_len = len_line*n
    # 用last_lines存储最后要处理的内容
    while True:
      # 如果要读取的1000个字符,大于之前存储的文件长度
      # 读完文件,直接break
      if read_len>self.file_length:
        self._file.seek(0)
        last_lines = self._file.read().split('\n')[-n:]
        break
      # 先读1000个 然后判断1000个字符里换行符的数量
      self._file.seek(-read_len, 2)
      last_words = self._file.read(read_len)
      # count是换行符的数量
      count = last_words.count('\n')
      if count>=n:
        # 换行符数量大于10 很好处理,直接读取
        last_lines = last_words.split('\n')[-n:]
        break
      # 换行符不够10个
      else:
        # break
        #不够十行
        # 如果一个换行符也没有,那么我们就认为一行大概是100个
        if count==0:
          len_perline = read_len
        # 如果有4个换行符,我们认为每行大概有250个字符
        else:
          len_perline = read_len/count
        # 要读取的长度变为2500,继续重新判断
        read_len = len_perline * n
    for line in last_lines:
      self.callback(line+'\n')
if __name__ == '__main__':
  py_tail = Tail('test.txt')
  py_tail.follow(20)

效果如下,终于可以打印最后几行了,大家可以试一下,无论日志每行只有1个,还是每行有300个字符,都是可以打印最后10行的

能做到这个地步,一般的面试官就直接被你搞定了,这个代码大概8分吧,如果还想再进一步,还有一些可以优化的地方,代码放github上了,有兴趣的可以拿去研究

待优化:留给大家作为扩展

  • 如果你tail -f的过程中,日志被备份清空或者切割了,怎么处理
  • 其实很简单,你维护一个指针位置,如果下次循环发现文件指针位置变了,从最新的指针位置开始读就行
  • 具体每种错误的类型

我这里只是简单的try 可以写个函数,把文件不存在,文件没权限这些报错信息分开

性能小优化,比如我第一次读了1000,只有4行,我大概估计每行250个字符,总体需要2500行,下次没必要直接读2500个,而是读1500行和之前1000行拼起来即可

最后大杀器 如果写出来这个,基本面试官会直接

打死你

import os
def tail(file_name):
  os.system('tail -f '+file_name)
tail('log.log')

总结

以上所述是小编给大家介绍的python实现tail -f 功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python中的jquery PyQuery库使用小结
May 13 Python
Python时间获取及转换知识汇总
Jan 11 Python
Python实现的排列组合计算操作示例
Oct 13 Python
Python中 传递值 和 传递引用 的区别解析
Feb 22 Python
Python GUI布局尺寸适配方法
Oct 11 Python
django 单表操作实例详解
Jul 30 Python
详解如何减少python内存的消耗
Aug 09 Python
Linux下通过python获取本机ip方法示例
Sep 06 Python
wxpython实现按钮切换界面的方法
Nov 19 Python
基于python图书馆管理系统设计实例详解
Aug 05 Python
python实现画图工具
Aug 27 Python
python numpy中setdiff1d的用法说明
Apr 22 Python
解决Python命令行下退格,删除,方向键乱码(亲测有效)
Jan 16 #Python
python对象销毁实例(垃圾回收)
Jan 16 #Python
python读取dicom图像示例(SimpleITK和dicom包实现)
Jan 16 #Python
.dcm格式文件软件读取及python处理详解
Jan 16 #Python
用python解压分析jar包实例
Jan 16 #Python
Python3 实现爬取网站下所有URL方式
Jan 16 #Python
python3爬取torrent种子链接实例
Jan 16 #Python
You might like
PHP中文处理 中文字符串截取(mb_substr)和获取中文字符串字数
2011/11/10 PHP
Linux Apache PHP Oracle 安装配置(具体操作步骤)
2013/06/17 PHP
PHP无限极分类函数的实现方法详解
2017/04/15 PHP
PHP实现简易计算器功能
2020/08/28 PHP
分享20款好玩的jQuery游戏
2011/04/17 Javascript
jquery动态增加text元素以及删除文本内容实例代码
2013/07/01 Javascript
使用js操作cookie的一点小收获分享
2013/09/03 Javascript
appendChild() 或 insertBefore()使用与区别介绍
2013/10/11 Javascript
Jquery在指定DIV加载HTML示例代码
2014/02/17 Javascript
js获取窗口相对于屏幕左边和上边的位置坐标
2014/05/15 Javascript
JavaScript数据结构与算法之链表
2016/01/29 Javascript
JS实现多级菜单中当前菜单不随页面跳转样式而发生变化
2017/05/30 Javascript
JavaScript实现三级联动菜单效果
2017/08/16 Javascript
bootstrap table sum总数量统计实现方法
2017/10/29 Javascript
JS实现百度搜索接口及链接功能实例代码
2018/02/02 Javascript
Node.js使用MySQL连接池的方法实例
2018/02/11 Javascript
Vue项目中最新用到的一些实用小技巧
2018/11/06 Javascript
Vue中的基础过渡动画及实现原理解析
2018/12/04 Javascript
js设计模式之单例模式原理与用法详解
2019/08/15 Javascript
[00:12]2018DOTA2亚洲邀请赛 sylar表现SOLO技艺
2018/04/06 DOTA
Python GAE、Django导出Excel的方法
2008/11/24 Python
详解Python的Django框架中manage命令的使用与扩展
2016/04/11 Python
Python3.7实现中控考勤机自动连接
2018/08/28 Python
使用Python实现从各个子文件夹中复制指定文件的方法
2018/10/25 Python
Python设计模式之备忘录模式原理与用法详解
2019/01/15 Python
Django与pyecharts结合的实例代码
2020/05/13 Python
详解python tkinter包获取本地绝对路径(以获取图片并展示)
2020/09/04 Python
通俗讲解python 装饰器
2020/09/07 Python
详解matplotlib绘图样式(style)初探
2021/02/03 Python
药品采购员岗位职责
2014/02/08 职场文书
男女朋友协议书
2014/04/23 职场文书
体育专业自荐书
2014/05/29 职场文书
宣传工作经验材料
2014/06/02 职场文书
离开雷锋的日子观后感
2015/06/09 职场文书
Pytorch GPU内存占用很高,但是利用率很低如何解决
2021/06/01 Python
企业版Windows 11有哪些新功能? Win11适用于企业的功能介绍
2021/11/21 数码科技