广告
返回顶部
首页 > 资讯 > 后端开发 > Python >使用Python实现tail的示例代码
  • 272
分享到

使用Python实现tail的示例代码

Python实现tailPythontail 2023-03-01 14:03:45 272人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录前记1.第一版--从文件尾部读取实时数据2.第二版--实现tail -f3.第三版--优雅的读取输出日志文件前记 tail是一个常用的linux命令, 它可以打印文件的后面n行数

前记

tail是一个常用的linux命令, 它可以打印文件的后面n行数据, 也能实时输出文件的追加数据. tail的实现很简单,但是要实现一个完善的tail却需要考虑很多细节,如果要注重性能,则需要引入一些其他的机制. 一开始只是为了单纯的实现Linux的tail的基本功能,后面随着需要对日志文件的高性能读取则需要的Linux的inotify机制去完善. 相关源码见:https://GitHub.com/so1n/example/tree/master/example_python/example_Python/tail

1.第一版--从文件尾部读取实时数据

主要思路是: 打开文件, 把指针移动到文件最后, 然后有数据则输出数据, 无数据则休眠一段时间.

import time
import sys

from typing import Callable, NoReturn


class Tail(object):
    def __init__(
            self,
            file_name: str,
            output: Callable[[str], NoReturn] = sys.stdout.write,
            interval: int = 1
    ):
        self.file_name: str = file_name
        self.output: Callable[[str], NoReturn] = output
        self.interval: int = interval

    def __call__(self):
        with open(self.file_name) as f:
            f.seek(0, 2)  # 从文件结尾处开始seek
            while True:
                line: str = f.readline()
                if line:
                    self.output(line)  # 使用print都会每次都打印新的一行
                else:
                    time.sleep(self.interval)


if __name__ == '__main__':
    filename: str = sys.argv[0]
    Tail(filename)()

之后只要做如下调用即可:

python xxx.py filename 

2.第二版--实现tail -f

tail -f默认先读取最后10行数据,再从文件尾部读取实时数据.如果对于小文件,可以先读取所有文件内容,并输出最后10行, 但是读取全文再获取最后10行的性能不高, 而从后滚10行的边界条件也很复杂, 先看先读取全文再获取最后10行的实现:

import time
import sys

from typing import Callable, NoReturn


class Tail(object):
    def __init__(
            self,
            file_name: str,
            output: Callable[[str], NoReturn] = sys.stdout.write,
            interval: int = 1
    ):
        self.file_name: str = file_name
        self.output: Callable[[str], NoReturn] = output
        self.interval: int = interval

    def __call__(self):
        with open(self.file_name) as f:
            self.read_last_line(f)
            while True:
                line: str = f.readline()
                if line:
                    self.output(line)  # 使用print都会每次都打印新的一行
                else:
                    time.sleep(self.interval)

    def read_last_line(self, f):
        last_lines = f.readlines()[-10:]
        for line in last_lines:
            self.output(line)

if __name__ == '__main__':
    filename: str = sys.argv[0]
    Tail(filename)()   

可以看到实现很简单, 相比第一版只多了个read_last_line的函数, 接下来就要解决性能的问题了, 当文件很大的时候, 这个逻辑是不行的, 特别是有些日志文件经常有几个G大, 如果全读出来内存就爆了. 而在Linux系统中, 没有一个接口可以指定指针跳到倒数10行, 只能使用如下方法来模拟输出倒数10行:

  • 首先游标跳转到最新的字符, 保存当前游标, 然后预估一行数据的字符长度, 最好偏多, 这里我按1024字符长度为一行来处理
  • 然后利用seek的方法,跳转到seek(-1024 * 10, 2)的字符, 这就是我们预估的倒数10行内的内容
  • 接着对内容进行判断, 如果跳转的字符长度小于 10 * 1024, 则证明整个文件没有10行, 则采用原来的read_last_line方法.
  • 如果跳转到字符长度等于1024 * 10, 则利用换行符计算已取字符长度共有多少行,如果行数大于10,那只输出最后10行,如果只读了4行,则继续读6*1024,直到读满10行为止

通过以上步奏, 就把倒数10行的数据计算好了可以打印出来, 可以进入追加数据了, 但是这时候文件内容可能发生改变了, 我们的游标也发生改变了, 这时候要把游标跳回到刚才保存的游标,防止漏打或者重复打印数据.

分析完毕后, 就可以开始重构read_last_line函数了.

import time
import sys

from typing import Callable, List, NoReturn


class Tail(object):
    def __init__(
            self,
            file_name: str,
            output: Callable[[str], NoReturn] = sys.stdout.write,
            interval: int = 1,
            len_line: int = 1024
    ):
        self.file_name: str = file_name
        self.output: Callable[[str], NoReturn] = output
        self.interval: int = interval
        self.len_line: int = len_line

    def __call__(self, n: int = 10):
        with open(self.file_name) as f:
            self.read_last_line(f, n)
            while True:
                line: str = f.readline()
                if line:
                    self.output(line)  # 使用print都会每次都打印新的一行
                else:
                    time.sleep(self.interval)

    def read_last_line(self, file, n):
        read_len: int = self.len_line * n

        # 跳转游标到最后
        file.seek(0, 2)
        # 获取当前结尾的游标位置
        now_tell: int = file.tell()
        while True:
            if read_len > file.tell():
                # 如果跳转的字符长度大于原来文件长度,那就把所有文件内容打印出来
                file.seek(0) # 由于read方法是按照游标进行打印, 所以要重置游标
                last_line_list: List[str] = file.read().split('\n')[-n:]
                # 重新获取游标位置
                now_tell: int = file.tell()
                break
            # 跳转到我们预估的字符位置
            file.seek(-read_len, 2)
            read_str: str = file.read(read_len)
            cnt: int = read_str.count('\n')
            if cnt >= n:
                # 如果获取的行数大于要求的行数,则获取前n行的行数
                last_line_list: List[str] = read_str.split('\n')[-n:]
                break
            else:
                # 如果获取的行数小于要求的行数,则预估需要获取的行数,继续获取
                if cnt == 0:
                    line_per: int = read_len
                else:
                    line_per: int = int(read_len / cnt)
                read_len = line_per * n

        for line in last_line_list:
            self.output(line + '\n')
        # 重置游标,确保接下来打印的数据不重复
        file.seek(now_tell)


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--filename")
    parser.add_argument("-n", "--num", default=10)
    args, unknown = parser.parse_known_args()
    if not args.filename:
        raise RuntimeError('filename args error')
    Tail(args.filename)(int(args.num))

3.第三版--优雅的读取输出日志文件

可以发现实时读取那块的逻辑性能还是很差, 如果每秒读一次文件,实时性就太慢了,把间隔改小了,则处理器占用太多. 性能最好的情况是如果能得知文件更新再进行打印文件, 那性能就能得到保障了.庆幸的是,在Linux中inotify提供了这样的功能. 此外,日志文件有一个特点就是会进行logrotate,如果日志被logrotate了,那我们就需要重新打开文件,并进一步读取数据, 这种情况也可以利用到inotify, 当inotify获取到文件重新打开的事件时,我们就重新打开文件,再读取.

import os
import sys

from typing import Callable, List, NoReturn

import pyinotify

multi_event = pyinotify.IN_MODIFY | pyinotify.IN_MOVE_SELF  # 监控多个事件


class InotifyEventHandler(pyinotify.ProcessEvent):  # 定制化事件处理类,注意继承
    """
    执行inotify event的封装
    """
    f: 'open()'
    filename: str
    path: str
    wm: 'pyinotify.WatchManager'
    output: Callable

    def my_init(self, **kargs):
        """pyinotify.ProcessEvent要求不能直接继承__init__, 而是要重写my_init, 我们重写这一段并进行初始化"""

        # 获取文件
        filename: str = kargs.pop('filename')
        if not os.path.exists(filename):
            raise RuntimeError('Not Found filename')
        if '/' not in filename:
            filename = os.getcwd() + '/' + filename
        index = filename.rfind('/')
        if index == len(filename) - 1 or index == -1:
            raise RuntimeError('Not a legal path')

        self.f = None
        self.filename = filename
        self.output: Callable = kargs.pop('output')
        self.wm = kargs.pop('wm')
        # 只监控路径,这样就能知道文件是否移动
        self.path = filename[:index]
        self.wm.add_watch(self.path, multi_event)

    def read_line(self):
        """统一的输出方法"""
        for line in self.f.readlines():
            self.output(line)

    def process_IN_MODIFY(self, event):
        """必须为process_事件名称,event表示事件对象, 这里表示监控到文件发生变化, 进行文件读取"""
        if event.pathname == self.filename:
            self.read_line()

    def process_IN_MOVE_SELF(self, event):
        """必须为process_事件名称,event表示事件对象, 这里表示监控到文件发生重新打开, 进行文件读取"""
        if event.pathname == self.filename:
            # 检测到文件被移动重新打开文件
            self.f.close()
            self.f = open(self.filename)
            self.read_line()

    def __enter__(self) -> 'InotifyEventHandler':
        self.f = open(self.filename)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.f.close()


class Tail(object):
    def __init__(
            self,
            file_name: str,
            output: Callable[[str], NoReturn] = sys.stdout.write,
            interval: int = 1,
            len_line: int = 1024
    ):
        self.file_name: str = file_name
        self.output: Callable[[str], NoReturn] = output
        self.interval: int = interval
        self.len_line: int = len_line

        wm = pyinotify.WatchManager()  # 创建WatchManager对象
        inotify_event_handler = InotifyEventHandler(
            **dict(filename=file_name, wm=wm, output=output)
        )  # 实例化我们定制化后的事件处理类, 采用**dict传参数
        wm.add_watch('/tmp', multi_event)  # 添加监控的目录,及事件
        self.notifier = pyinotify.Notifier(wm, inotify_event_handler)  # 在notifier实例化时传入,notifier会自动执行
        self.inotify_event_handle: 'InotifyEventHandler' = inotify_event_handler

    def __call__(self, n: int = 10):
        """通过inotify的with管理打开文件"""
        with self.inotify_event_handle as i:
            # 先读取指定的行数
            self.read_last_line(i.f, n)
            # 启用inotify的监听
            self.notifier.loop()

    def read_last_line(self, file, n):
        read_len: int = self.len_line * n

        # 获取当前结尾的游标位置
        file.seek(0, 2)
        now_tell: int = file.tell()
        while True:
            if read_len > file.tell():
                # 如果跳转的字符长度大于原来文件长度,那就把所有文件内容打印出来
                file.seek(0)
                last_line_list: List[str] = file.read().split('\n')[-n:]
                # 重新获取游标位置
                now_tell: int = file.tell()
                break
            file.seek(-read_len, 2)
            read_str: str = file.read(read_len)
            cnt: int = read_str.count('\n')
            if cnt >= n:
                # 如果获取的行数大于要求的行数,则获取前n行的行数
                last_line_list: List[str] = read_str.split('\n')[-n:]
                break
            else:
                # 如果获取的行数小于要求的行数,则预估需要获取的行数,继续获取
                if cnt == 0:
                    line_per: int = read_len
                else:
                    line_per: int = int(read_len / cnt)
                read_len = line_per * n

        for line in last_line_list:
            self.output(line + '\n')
        # 重置游标,确保接下来打印的数据不重复
        file.seek(now_tell)


if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--filename")
    parser.add_argument("-n", "--num", default=10)
    args, unknown = parser.parse_known_args()
    if not args.filename:
        raise RuntimeError('filename args error')
    Tail(args.filename)(int(args.num))

可以看到, 从原本的open打开文件改为用inotify打开文件(这时候会调用my_init方法进行初始化), 打开后还是运行我们打开原来n行的代码, 然后就交给inotify运行. 在inotify运行之前, 我们把重新打开文件方法和打印文件方法都挂载在inotifiy对应的事件里, 之后inotify运行时, 会根据对应的事件执行对应的方法.

到此这篇关于使用Python实现tail的示例代码的文章就介绍到这了,更多相关Python tail内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 使用Python实现tail的示例代码

本文链接: https://www.lsjlt.com/news/198021.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 使用Python实现tail的示例代码
    目录前记1.第一版--从文件尾部读取实时数据2.第二版--实现tail -f3.第三版--优雅的读取输出日志文件前记 tail是一个常用的Linux命令, 它可以打印文件的后面n行数...
    99+
    2023-03-01
    Python实现tail Python tail
  • 怎么使用Python实现tail
    本篇内容介绍了“怎么使用Python实现tail”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.第一版--从文件尾部读取实时数据主要思路是...
    99+
    2023-07-05
  • 使用python实现定时报天气的示例代码
    前言 如果你和我一样偶尔看看股票,看看自己关注的股票是涨了还是跌了,或者想快速获取到想看的头条新闻,我们不必把过多的注意力放在去寻找上面,我们只需要让爬虫程序每天自动为你发送你想要了...
    99+
    2022-11-12
  • Python实现PING命令的示例代码
    目录一、PING简介二、代码实现        三、结果显示一、PING简介 PING(Packet Internet Grope)...
    99+
    2023-01-06
    Python PING命令 Python PING
  • python实现跳表SkipList的示例代码
    跳表 跳表,又叫做跳跃表、跳跃列表,在有序链表的基础上增加了“跳跃”的功能,由William Pugh于1990年发布,设计的初衷是为了取代平衡树(比如红黑树)。 Redis、LevelDB 都是著名的 Key-Va...
    99+
    2022-06-02
    python 跳表SkipList python 跳表
  • python中Switch/Case实现的示例代码
    学习Python过程中,发现没有switch-case,过去写C习惯用Switch/Case语句,官方文档说通过if-elif实现。所以不妨自己来实现Switch/Case功能。 使用if…elif…el...
    99+
    2022-06-04
    示例 代码 python
  • python实现自幂数的示例代码
    1、什么是自幂数? 前文介绍过 python 实现水仙花数,其实水仙花数为自幂数的一种,即,3位自幂数。 自幂数是指一个 n 位数,它的每个位上的数字的 n 次幂之和等于它...
    99+
    2022-11-11
  • Python实现计算AUC的示例代码
    目录为什么这样一个指标可以衡量分类效果auc理解AUC计算方法一方法二实现及验证AUC(Area under curve)是机器学习常用的二分类评测手段,直接含义是ROC曲线下的面积...
    99+
    2022-11-11
  • Python实现PDF转MP3的示例代码
    目录一、PDF转为MP3 二、准备工作三、代码很简单四、变更播音员总结一、PDF转为MP3  我们平常看到很多文件都是PDF格式,网上的各类书籍多为此格式。有时候...
    99+
    2023-05-18
    Python实现PDF转MP3 Python PDF转MP3 Python PDF MP3
  • Python实现连点器的示例代码
    啊,为此我特意准备了两个程序,一个是用来测试的,一个是主程序。来看看吧 直接放连点器代码: # 改进版 import pyautogui as pag from time impor...
    99+
    2022-11-13
  • Python实现屏幕代码雨效果的示例代码
    直接上代码 import pygame import random def main(): # 初始化pygame pygame.init() #...
    99+
    2022-11-13
  • Python实现 MK检验示例代码
    MK检验:时间序列进行检测,并找出突变点,本文参考网上的matlab程序改写为python代码如下: import numpy as np import pandas as pd...
    99+
    2022-11-12
  • Python使用Qt5实现水平导航栏的示例代码
    在 Qt5 中可以使用 QWidget 包含两个水平布局,通过点击水平布局里的按钮,实现下标滑动与页面的切换。 可以按照以下步骤来实现上面图片中的功能: 导入必要的 Qt 包: f...
    99+
    2023-03-06
    Qt5 水平导航栏 Qt5 导航栏
  • Python实现视频裁剪的示例代码
    目录前言环境依赖代码验证一下前言 本文提供将视频按照自定义尺寸进行裁剪的工具方法,一如既往的实用主义。 环境依赖 ffmpeg环境安装,可以参考文章:windows ffmpeg安装...
    99+
    2022-11-13
  • Python实现边缘提取的示例代码
    目录复习一、边缘提取1、什么是边缘2、什么是边缘提取二、Sobel算子三、Canny边缘检测算法1、算法步骤2、高斯平滑3、非极大值抑制4、双阈值检测四、相关代码复习 (1)梯度: ...
    99+
    2022-11-11
  • python实现图像识别的示例代码
    一、安装库 首先我们需要安装PIL和pytesseract库。 PIL:(Python Imaging Library)是Python平台上的图像处理标准库,功能非常强大。 pyte...
    99+
    2022-11-11
  • Python实现批量翻译的示例代码
    目录截图源码Translator.pyLog.pyUtils.py简单的使用案例Python版本截图 源码 Translator.py #!/usr/bin/python # -*...
    99+
    2022-11-11
  • python实现线性回归的示例代码
    目录1线性回归1.1简单线性回归1.2 多元线性回归1.3 使用sklearn中的线性回归模型1线性回归 1.1简单线性回归 在简单线性回归中,通过调整a和b的参数值,来拟合从x到...
    99+
    2022-11-13
  • Python实现一键抠图的示例代码
    目录需求来源实现方法需求来源 好友 A:橡皮擦,可否提供网页,上传带人像的图片,然后可以直接抠图,最好直接生成 PNG 图片下载。 橡皮擦:每天需要调用多少次? 好友 A:大概 10...
    99+
    2022-11-11
  • python实现进制转化的示例代码
    做题思路 (1)掌握十进制转化为其他进制的方法 (2)分析和解决如何将整数和小数分离,以及他们的存储方式。(3)如何设计python函数去实现它们 (4)了解辗转相除法和列表如何运用...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作