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 list转dict示例分享
Jan 28 Python
Python单例模式实例分析
Jan 14 Python
通过Python爬虫代理IP快速增加博客阅读量
Dec 14 Python
Python使用getpass库读取密码的示例
Oct 10 Python
Python调用C语言的方法【基于ctypes模块】
Jan 22 Python
Python实现输出某区间范围内全部素数的方法
May 02 Python
Django REST framework视图的用法
Jan 16 Python
python单例模式的多种实现方法
Jul 26 Python
python实现可下载音乐的音乐播放器
Feb 25 Python
对python中各个response的使用说明
Mar 28 Python
Python开发入门——迭代的基本使用
Sep 03 Python
Python + opencv对拍照得到的图片进行背景去除的实现方法
Nov 18 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
MYSQL环境变量设置方法
2007/01/15 PHP
php MessagePack介绍
2013/10/06 PHP
PHP字符串长度计算 - strlen()函数使用介绍
2013/10/15 PHP
md5 16位二进制与32位字符串相互转换示例
2013/12/30 PHP
php实现微信分享朋友链接功能
2019/02/18 PHP
List Installed Software Features
2007/06/11 Javascript
ie8模式下click无反应点击option无反应的解决方法
2014/10/11 Javascript
纯javascript实现简单下拉刷新功能
2015/03/13 Javascript
jQuery检查元素存在性(推荐)
2016/09/17 Javascript
Nodejs高扩展性的模板引擎 functmpl简介
2017/02/13 NodeJs
vue+vuex+axio从后台获取数据存入vuex实现组件之间共享数据
2017/04/22 Javascript
详谈Angular 2+ 的表单(一)之模板驱动型表单
2017/04/25 Javascript
Javascript中Promise的四种常用方法总结
2017/07/14 Javascript
你有必要知道的10个JavaScript难点
2017/07/25 Javascript
angular或者js怎么确定选中ul中的哪几个li
2017/08/16 Javascript
webpack的CSS加载器的使用
2018/09/11 Javascript
antd Upload 文件上传的示例代码
2018/12/14 Javascript
使用element-ui table expand展开行实现手风琴效果
2019/03/15 Javascript
vue 详情跳转至列表页实现列表页缓存
2019/03/27 Javascript
jquery选择器和属性对象的操作实例分析
2020/01/10 jQuery
vue实现分页的三种效果
2020/06/23 Javascript
跟老齐学Python之重回函数
2014/10/10 Python
Python3.x中自定义比较函数
2015/04/24 Python
python放大图片和画方格实现算法
2018/03/30 Python
Django中数据库的数据关系:一对一,一对多,多对多
2018/10/21 Python
Python通用循环的构造方法实例分析
2018/12/19 Python
Python 串口读写的实现方法
2019/06/12 Python
Python 运行.py文件和交互式运行代码的区别详解
2019/07/02 Python
python的移位操作实现详解
2019/08/21 Python
Python实现代码统计工具
2019/09/19 Python
Python中输入和输出(打印)数据实例方法
2019/10/13 Python
django之导入并执行自定义的函数模块图解
2020/04/01 Python
PyQt5结合matplotlib绘图的实现示例
2020/09/15 Python
pycharm 的Structure界面设置操作
2021/02/05 Python
委托证明范本
2014/11/25 职场文书
基于CSS3画一个iPhone
2021/04/21 HTML / CSS