Распечатайте несколько предыдущих строк после совпадения строки, найденной в строке в python

Я пишу программу для анализа некоторых файлов журнала. Если в строке есть код ошибки, мне нужно распечатать предыдущие 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 для этого, но мне не очень повезло, потому что я не знаю, как чтобы внедрить диапазон в файлы.


person pjano1    schedule 24.08.2018    source источник


Ответы (2)


Это идеальный пример использования ограниченной длины 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), чтобы индексация имела смысл.

Что касается файла журнала, вы должны предположить, что он может быть довольно большим (я регулярно имею дело с файлами журнала размером в несколько гигабайт), до такой степени, что загрузка его в память приведет к исчерпанию основной памяти, поэтому хлюпание — плохая идея, и повторное чтение файла с нуля каждый раз, когда вы сталкиваетесь с ошибкой, почти так же плохо (это медленно, но надежно медленно, я думаю?). Подход, основанный на 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, чтобы ограничить свой фрагмент.

Обратите внимание, что даже тогда я не использовал range; range — это костыль, на который полагаются многие люди, пришедшие из C, но обычно это неправильный способ решения проблемы в Python. В тех случаях, когда индекс необходим (обычно это не так), обычно требуется и значение, поэтому решения на основе enumerate предпочтительнее; в большинстве случаев вам вообще не нужен индекс, поэтому правильным решением является прямая итерация (или парная итерация с zip и т.п.).

person ShadowRanger    schedule 24.08.2018
comment
мы можем использовать queue здесь? - person Van Peer; 24.08.2018
comment
@VanPeer: queue.Queue предназначен для переключения между потоками/процессами. Когда вы ограничиваете длину queue.Queue, он блокируется при достижении предела, он не отбрасывает молча самое старое значение; в этом случае нам нужна функция молчаливого отбрасывания самого старого значения. queue.Queue также добавляет массу накладных расходов, которые здесь ничего не дают; на самом деле он построен на collections.deque под капотом, но там, где collections.deque реализовано на C и без блокировки, queue.Queue реализовано на Python с использованием довольно сложного кода синхронизации. - person ShadowRanger; 24.08.2018
comment
Примечание. Если вы хотите дважды печатать строки для ошибок в непосредственной близости, вы должны просто удалить строки line_history.clear() и else:, а затем убрать line_history.append(line) (так что это выполняется безоговорочно). Я пошел с очисткой, потому что следовал дизайну, более близкому к поведению fgrep -B25 'error code', которое этот код примерно воспроизводит. - person ShadowRanger; 24.08.2018
comment
Спасибо за объяснение! Я попробовал, используя queue. Мы должны написать отдельную логику для выселения, когда он заполнится. - person Van Peer; 24.08.2018
comment
@ShadowRanger, я знаю, что уже слишком поздно, пока я использовал ваше решение и просто пытался найти способ удалить newline при использовании print(*line_history, line, sep=''), также было бы здорово, если бы вы могли немного рассказать здесь о значении звездного символа *line_history, я пытался чтобы проверить это, но не в состоянии получить это, будучи новичком. - person kulfi; 21.02.2020
comment
@ShadowRanger, большое спасибо, что указали и поделились ссылкой. - person kulfi; 21.02.2020

Попробуйте базовый код с циклом 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])

Выше печатаются предыдущие 25 строк или с первой строки, если общее количество строк меньше 25.

Кроме того, лучше избегать использования input в качестве переменной термина, так как это ключевое слово в Python.

person rnso    schedule 24.08.2018
comment
Хотя это работает для небольших файлов журналов, у него есть пиковые требования к памяти, пропорциональные удвоенному общему размеру файла журнала (строка input = f.read().splitlines() должна, хотя и ненадолго, содержать весь файл в памяти как str, а также list, содержащий str для каждой строки в файле). Для файла журнала объемом 1 ГБ вам лучше иметь 2,7-12 ГБ ОЗУ (помимо того, что использует ваша ОС, интерпретатор Python и все другие ваши программы, точное количество зависит от того, является ли текст ASCII, латиницей). 1, BMP или не-BMP), чтобы хранить все это, иначе вы застрянете в аду обмена свопами. - person ShadowRanger; 24.08.2018
comment
Это очень хороший момент, но я подумал, что новичку больше будет интересен простой код, чем стандартный подход. - person rnso; 24.08.2018