【问题标题】:Print a number of previous lines after matching string found in line in python在 python 中的 line 中找到匹配字符串后打印前几行
【发布时间】:2018-08-24 18:16:00
【问题描述】:

我正在编写一个程序来解析一些日志文件。如果行中有错误代码,我需要打印前 25 行进行分析。我希望能够根据各个错误代码(而不是 25 行、15 或 35 行)用更多或更少的行来重复这个概念。

with open(file, 'r') as input:
     for line in input:
         if "error code" in line: 
             #print previous 25 lines

我知道 Bash 中我需要的等效命令是 grep "error code" -B 25 Filename | wc -1。我对 python 和编程还是很陌生,我知道我需要一个 for 循环,我已经尝试使用 range 函数来做到这一点,但我运气不佳,因为我没有'不知道如何将范围实现到文件中。`

【问题讨论】:

    标签: python text-parsing fileparsing


    【解决方案1】:

    这是a length limited collections.deque 的完美用例:

    from collections import deque
    
    line_history = deque(maxlen=25)
    with open(file) as input:
        for line in input:
            if "error code" in line: 
                print(*line_history, line, sep='')
                # Clear history so if two errors seen in close proximity, we don't
                # echo some lines twice
                line_history.clear()
            else:
                # When deque reaches 25 lines, will automatically evict oldest
                line_history.append(line)
    

    我为什么选择这种方法的完整解释(如果你不关心,请跳过):

    使用for/range 无法以良好/安全的方式解决此问题,因为索引只有在将整个文件加载到内存时才有意义;磁盘上的文件不知道行的开始和结束位置,因此您不能只要求“文件的第 357 行”而不从头开始读取它以找到第 1 行到第 356 行。您最终会反复重读文件,或将整个文件放入内存序列(例如list/tuple)以使索引有意义。

    对于一个日志文件,您必须假设它可能非常大(我经常处理数 GB 的日志文件),以至于将其加载到内存中会耗尽主内存,所以 slurping 是一个坏主意,并且每次遇到错误时从头开始重新读取文件几乎一样糟糕(它很慢,但我猜它确实很慢?)。基于deque 的方法意味着您的峰值内存使用量基于文件中最长的 27 行,而不是文件总大小。

    只有内置插件的天真的解决方案可能很简单:

    with open(file) as input:
        lines = tuple(input)  # Slurps all lines from file
    for i, line in enumerate(lines):
        if "error code" in line:
            print(*lines[max(i-25, 0):i], line, sep='')
    

    但就像我说的那样,这需要足够的内存来一次将整个日志文件保存在内存中,这是一件很糟糕的事情。当两个错误非常接近时,它也会重复行,因为与deque 不同,你没有一个简单的方法来清空你最近的记忆;您必须手动跟踪最后一个 print 的索引以限制您的切片。

    请注意,即便如此,我也没有使用rangerange 是许多来自 C 背景的人所依赖的拐杖,但它通常是用 Python 解决问题的错误方法。在需要(通常不需要)索引的情况下,您通常也需要该值,因此基于enumerate 的解决方案更胜一筹;大多数时候,您根本不需要索引,因此直接迭代(或与zip 或类似的配对迭代)是正确的解决方案。

    【讨论】:

    • 我们可以在这里使用queue吗?
    • @VanPeer: queue.Queue 用于线程/进程之间的切换。当您限制queue.Queue 的长度时,它会在达到限制时阻塞,它不会默默地丢弃最旧的值;在这种情况下,默默地丢弃最旧的值是我们想要的功能。 queue.Queue 还增加了大量的开销,在这里你什么也得不到;它实际上是建立在底层的collections.deque 之上,但是collections.deque 是用C 实现并且无锁的,queue.Queue 是用Python 实现的,使用相当复杂的同步代码。
    • 注意:如果您想要为近距离错误打印两次,您只需删除line_history.clear()else:行,然后删除line_history.append(line) (所以它是无条件执行的)。我进行了清除,因为我遵循的设计更接近 fgrep -B25 'error code' 的行为,此代码大致复制了该行为。
    • 感谢您的解释!我确实尝试过使用queue。我们必须编写单独的逻辑来在它满时驱逐。
    • @ShadowRanger,我知道这为时已晚,而我正在使用您的解决方案,只是想在使用 print(*line_history, line, sep='') 时找到删除 newline 的方法,如果可以的话,也很棒在这里介绍一下*line_history 星字符的意义,我试图检查它,但无法让它成为一个新手学习者。
    【解决方案2】:

    尝试使用for 循环和range 函数而不使用任何特殊库的基本编码:

    N = 25
    with open(file, 'r') as f:
        lines = f.read().splitlines()
        for i, line in enumerate(lines):
            if "error code" in line: 
                j = i-N if i>N else 0
                for k in range(j,i):
                    print(lines[k])
    

    Above 打印前 25 行,如果总行数少于 25 行,则从第一行打印。

    另外,最好避免使用input 作为变量项,因为它是 Python 中的关键字。

    【讨论】:

    • 虽然这适用于小日志文件,但它的峰值内存需求与日志文件总大小的两倍成正比(input = f.read().splitlines() 行必须,无论多么短暂,将整个文件保存在内存中作为str,以及在文件中的每一行包含strlist)。对于 1 GB 的日志文件,您最好有 2.7-12 GB 的 RAM(在您的操作系统、Python 解释器和所有其他程序正在使用的任何东西之上,具体数量取决于文本是 ASCII 还是 latin- 1, BMP 或非 BMP) 可用于保存所有内容,否则您将陷入交换颠簸地狱。
    • 这是一个很好的观点,但我认为初学者对简单代码比对行业标准方法更感兴趣。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 1970-01-01
    • 2016-10-23
    • 2021-06-29
    • 2016-08-26
    • 2014-12-18
    相关资源
    最近更新 更多