当前位置:

首页 > 编程开发 > 怎么使用Python实现tail

怎么使用Python实现tail

1.第一版--从文件尾部读取实时数据主要思路是:打开文件,把指针移动到文件最后,然后有数据则输出数据,无数据则休眠一段时间.importtimeimportsysfromtypingimportCallable,NoReturnclassTail(object):def__init__(self,file_name:str,output:Callable[[str],NoReturn]=sys.stdout.write,interval:int=1):self.file_name:str=file_nam

本文目录

    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运行时, 会根据对应的事件执行对应的方法。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Python tail
    相关文章 更多
    python删除一个列表里的重复元素的方法与代码示例
    python删除一个列表里的重复元素的方法与代码示例

    学习如何使用Python高效删除列表中的重复元素。本文提供5种常见方法的代码示例,涵盖保持顺序与不保持顺序的场景,适合初学者和进阶开发者参考。

    Python写接口的步骤及完整代码示例
    Python写接口的步骤及完整代码示例

    详细讲解使用Python编写Web接口的核心步骤与完整代码示例,涵盖环境准备、路由定义、数据校验、服务启动及调试测试方法。

    codex怎么读取word文档及docx文件操作教程
    codex怎么读取word文档及docx文件操作教程

    介绍如何利用编程工具读取和解析Word文档(docx格式),涵盖基础文本提取代码示例、结构化数据处理逻辑,以及处理复杂排版时的技术局限与替代方案。

    怎么在Python中调用C语言代码提升性能
    怎么在Python中调用C语言代码提升性能

    详细讲解在Python中调用C语言代码提升性能的完整实践指南,包含原理分析、编译步骤、ctypes与CFFI具体实现、性能对比及常见陷阱。

    Python与C语言的性能差异及底层原理
    Python与C语言的性能差异及底层原理

    深入解析Python与C语言在执行效率上的数量级差异,从编译机制、变量内存布局、动态类型推断到GIL锁,系统阐明底层核心原理。

    Python和C语言哪个更适合编程入门
    Python和C语言哪个更适合编程入门

    客观对比Python与C语言在心智负担、底层机制与学习反馈上的核心差异,提供清晰有据的选择标准与切实的入门学习路径。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    langgraph python guide中文入门教程详解
    langgraph python guide中文入门教程详解

    本篇LangGraph Python中文入门教程详解,系统梳理StateGraph状态机定义、节点编写、边与条件路由、检查点持久化(MemorySaver)等核心机制,提供从环境安装到完整循环智能体构建的代码示例与实操指导。

    Windows 安装 Python:从官方入口到虚拟环境的可复现实践
    Windows 安装 Python:从官方入口到虚拟环境的可复现实践

    面向后端开发者,基于 Python 官方文档说明如何在 Windows 上安装 Python、创建 venv 虚拟环境及使用 pip 管理依赖。包含可复制命令、验证方法及工具选择边界,避免常见配置误区。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。