Вопрос или проблема
У меня есть файл журнала, который содержит отчет о выводе процесса. Я хочу извлечь все строки между последним вхождением двух шаблонов.
Шаблоны будут выглядеть следующим образом:
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: утилита для инверсии порядка строк в файле, чем удобно пользоваться в задачах, где важен порядок поиска.
Примеры
-
Использование
sed
иtac
:Можно воспользоваться комбинацией утилит для извлечения последнего блока текста между шаблонами. Пример команды:
tac fileName | sed -n '/EndPattern/,$p; /StartPattern/q' | tac
tac
: переворачивает файл, чтобы сначала найти последнийEndPattern
.sed
: извлекает все доStartPattern
и заканчивает выполнение.- Второй вызов
tac
: возвращает текст в изначальный порядок.
-
Варианты с
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
.
- Внутренняя переменная
-
GNU
sed
решение:Это вариант, использующий hold space:
sed '/START/{:1;$!{/END/!{N;b1};h}};${x;p};d' filename
- Каждое появление
START
перезаписывает hold space. - В конце файла выводится содержимое hold space до последнего полноценного блока
START
–END
.
- Каждое появление
Применение
Данные подходы актуальны для администраторов систем и IT-специалистов, занимающихся обработкой логов, мониторингом систем и другими задачами, где работа с текстовыми файлами имеет критическое значение. Для автоматизации подобных задач рекомендуется создавать скрипты с описанными командами, что позволит обрабатывать логи в пакетном режиме или в процессе регулярного мониторинга системы.
Таким образом, зная возможности и ограничения каждого инструмента, можно эффективно решать задачи извлечения данных из файлов. Необходимо выбирать инструмент в зависимости от сложности задачи, объемов данных и доступных ресурсов. Выбор между sed
, awk
и их комбинацией с tac
зависит от необходимой функциональности и производительности.