Как получить последнее вхождение строк между двумя шаблонами из файла?

Вопрос или проблема

У меня есть файл журнала, который содержит отчет о выводе процесса. Я хочу извлечь все строки между последним вхождением двух шаблонов.

Шаблоны будут выглядеть следующим образом:

Summary process started at <datestring>

и

Summary process finished at <datestring> with return code <num>

В файле будет несколько экземпляров этих шаблонов, а также много другой информации. Я хотел бы напечатать только последнее вхождение.

Я знаю, что могу использовать:

sed -n '/StartPattern/,/EndPattern/p' FileName

чтобы получить строки между шаблонами, но не уверен, как получить последний экземпляр.

Решения с использованием sed или awk будут в порядке.

Редактировать:
Я недостаточно ясно выразился о поведении, которое хочу, в случае если несколько StartPattern появляются без EndPattern, или если нет EndPattern перед концом файла после обнаружения StartPattern.

  • Для нескольких StartPattern с отсутствующим EndPattern, я хотел бы получить строки только от последнего StartPattern до EndPattern.
  • Для StartPattern, который достигает EOF без EndPattern, я хотел бы получить всё до EOF, с последующим предупреждением о том, что EOF был достигнут преждевременно.

Вы всегда можете сделать следующее:

tac < fileName | sed  '/EndPattern/,$!d;/StartPattern/q' | tac

Если в вашей системе нет GNU tac, вы можете использовать tail -r вместо него.

Вы также можете сделать это так:

awk '
  inside {
    text = text $0 RS
    if (/EndPattern/) inside=0
    next
  }
  /StartPattern/ {
    inside = 1
    text = $0 RS
  }
  END {printf "%s", text}' < filename

Но это означает чтение всего файла.

Учтите, что это может давать разные результаты, если между StartPattern и следующим EndPattern есть другой StartPattern, или если последнее StartPattern не имеет завершающего EndPattern, или если есть строки, совпадающие как с StartPattern, так и с EndPattern.

awk '
  /StartPattern/ {
    inside = 1
    text = ""
  }
  inside {text = text $0 RS}
  /EndPattern/ {inside = 0} 
  END {printf "%s", text}' < filename

Это позволит ему вести себя более похоже на подход tac+sed+tac (за исключением случая с незакрытым последним StartPattern).

Последнее похоже на ваши отредактированные требования. Чтобы добавить предупреждение, достаточно просто:

awk '
  /StartPattern/ {
    inside = 1
    text = ""
  }
  inside {text = text $0 RS}
  /EndPattern/ {inside = 0} 
  END {
    printf "%s", text
    if (inside)
      print "Warning: EOF reached without seeing the end pattern" > "/dev/stderr"
  }' < filename

Чтобы избежать чтения всего файла:

tac < filename | awk '
  /StartPattern/ {
    printf "%s", $0 RS text
    if (!inside)
      print "Warning: EOF reached without seeing the end pattern" > "/dev/stderr"
    exit
  }
  /EndPattern/ {inside = 1; text = ""}
  {text = $0 RS text}'

Примечание о портативности: для /dev/stderr вам нужна либо система с таким специальным файлом (учтите, что в Linux если stderr открыт на файле, допускающим поиск, это может записать текст в начало файла вместо текущей позиции в файле), либо реализация awk, которая это эмулирует, как gawk, mawk или busybox awk (они обходят упомянутую проблему Linux выше).

На других системах вы можете заменить print ... > "/dev/stderr" на print ... | "cat>&2".

Вы можете использовать GNU sed следующим образом

sed '/START/{:1;$!{/END/!{N;b1};h}};${x;p};d' file

Просто перезаписывает пространство удержания при каждом вхождении полного многострочного шаблона. Печатает его в конце файла.

Это обеспечит последовательное поведение, такое как

  • Оба START и END на одной строке, совпадение с этой строкой.
  • Несколько START после начального START, совпадение всех до END.
  • Не будет печатать совпадение, если нет END, будет печатать последнее вхождение полного START до END.

С GNU sed другое решение может быть (с переменными P1/P2 как начальный/конечный шаблоны) :

sed -n "/${P1}/,/${P2}/H; /${P1}/h; \${g;p}"

Основные отличия от решения @Stéphane Chazelas в том, что здесь:

  • если до последнего END/EOF несколько START, отображается от последнего START до последнего END/EOF.
  • любой END на той же строке, что и START, игнорируется
  • последний END в последней строке ввода поддерживается
  • если после последнего START нет END, мы печатаем от последнего START до EOF

Вот решение с использованием awk:

awk '/EndPattern/ {recording=0}  recording>0 {buffer=buffer $0 "\n"}  /StartPattern/ {recording+=1; buffer=""}  END {printf "%s", buffer; if(recording>0) {print "WARNING: missing EndPattern" > "/dev/stderr"}}'

Таким образом, для следующего ввода:

1
StartPattern
2
3
EndPattern
4
5
StartPattern
6
7
EndPattern
8

Вы получите следующий вывод:

6
7

Пожалуйста, замените StartPattern на ^StartPattern$, если вы хотите точное совпадение строки, то же самое для EndPattern. Также замените recording+=1 на recording=1, если вы хотите игнорировать вложенные шаблоны.

Мне нравится ответ от Stéphane Chazelas о комбинировании sed и tac, однако, мне не нравится часть с использованием $!d, чтобы удалить остальное, так как это довольно сложно для чтения/понимания.

Мне больше нравится следующая комбинация без $!d, которая легче для чтения/понимания:

tac fileName | sed -n '/EndPattern/,$p; /StartPattern/q' | tac

Что означает:

  • Он переворачивает содержимое файла (tac)
  • Находит первый EndPattern, затем печатает их все ($p)
  • Далее доходит до первого StartPattern, затем завершает (q)
  • Снова переворачивает порядок обратно к норме.

Однако с точки зрения производительности, я считаю, что ответ от Bruno лучше, так как он не требует tac для переворачивания порядка содержимого. (Но с другой стороны, это немного сложнее для понимания, так как требуется копирование и замена между пространством удержания и пространством шаблона…)

Ответ или решение

Для того чтобы получить последние вхождения строк между двумя шаблонами в файле, можно использовать утилиты командной строки, такие как sed, awk или tac. Это задача часто возникает при анализе логов, где необходимо извлечь определенную информацию между фиксированными паттернами, которые могут повторяться в файле. Рассмотрим этот вопрос более детально.

Теория

Основная задача — это извлечение строк, находящихся между двумя шаблонами: начальным (StartPattern) и конечным (EndPattern). Часто в текстовых файлах, таких как логи, данные содержатся в повторяющихся блоках, каждый из которых начинается и заканчивается с определенными ключевыми словами.

Использование sed, awk и tac позволяет гибко обрабатывать такие файлы. Каждый из этих инструментов обладает своими уникальными возможностями:

  • sed: потоковый редактор, применяется для фильтрации и трансформации текста в потоке.
  • awk: язык программирования, ориентированный на обработку текстовых данных, может выполнять более сложные операции, включая выполнения скриптов.
  • tac: утилита для инверсии порядка строк в файле, чем удобно пользоваться в задачах, где важен порядок поиска.

Примеры

  1. Использование sed и tac:

    Можно воспользоваться комбинацией утилит для извлечения последнего блока текста между шаблонами. Пример команды:

    tac fileName | sed -n '/EndPattern/,$p; /StartPattern/q' | tac
    • tac: переворачивает файл, чтобы сначала найти последний EndPattern.
    • sed: извлекает все до StartPattern и заканчивает выполнение.
    • Второй вызов tac: возвращает текст в изначальный порядок.
  2. Варианты с awk:

    awk поддерживает более сложную логику, что позволяет управлять состоянием между начальным и конечным шаблонами:

    awk '
     /StartPattern/ {inside = 1; text = ""}
     /EndPattern/ {inside = 0} 
     inside {text = text $0 "\n"}
     END {printf "%s", text; if (inside) print "Warning: EOF without EndPattern" > "/dev/stderr"}' fileName
    • Внутренняя переменная inside позволяет контролировать запись текста между шаблонами.
    • По заверешению, если inside все еще истинно, выдает предупреждение о достижении EOF без EndPattern.
  3. GNU sed решение:

    Это вариант, использующий hold space:

    sed '/START/{:1;$!{/END/!{N;b1};h}};${x;p};d' filename
    • Каждое появление START перезаписывает hold space.
    • В конце файла выводится содержимое hold space до последнего полноценного блока STARTEND.

Применение

Данные подходы актуальны для администраторов систем и IT-специалистов, занимающихся обработкой логов, мониторингом систем и другими задачами, где работа с текстовыми файлами имеет критическое значение. Для автоматизации подобных задач рекомендуется создавать скрипты с описанными командами, что позволит обрабатывать логи в пакетном режиме или в процессе регулярного мониторинга системы.

Таким образом, зная возможности и ограничения каждого инструмента, можно эффективно решать задачи извлечения данных из файлов. Необходимо выбирать инструмент в зависимости от сложности задачи, объемов данных и доступных ресурсов. Выбор между sed, awk и их комбинацией с tac зависит от необходимой функциональности и производительности.

Оцените материал
Добавить комментарий

Капча загружается...