Как выбрать линии между двумя узорами?
У меня есть файл, подобный следующему, и я хотел бы напечатать линии между двумя данными образцами PAT1
а также PAT2
,
1
2
PAT1
3 - first block
4
PAT2
5
6
PAT1
7 - second block
PAT2
8
9
PAT1
10 - third block
Я прочитал, как выбрать линии между двумя шаблонами маркеров, которые могут встречаться несколько раз с помощью awk / sed, но мне любопытно увидеть все возможные комбинации этого, либо печать шаблона, либо нет.
Как я могу выбрать линии между двумя узорами?
6 ответов
Печать строк между PAT1 и PAT2
$ awk '/PAT1/,/PAT2/' file
PAT1
3 - first block
4
PAT2
PAT1
7 - second block
PAT2
PAT1
10 - third block
Или, используя переменные:
awk '/PAT1/{flag=1} flag; /PAT2/{flag=0}' file
Как это работает?
/PAT1/
соответствует строкам, содержащим этот текст, а также/PAT2/
делает./PAT1/{flag=1}
устанавливаетflag
когда текстPAT1
находится в строке./PAT2/{flag=0}
снимаетflag
когда текстPAT2
находится в строке.flag
шаблон с действием по умолчанию, которое заключается вprint $0
: еслиflag
равен 1, строка печатается. Таким образом, он напечатает все те строки, которые произошли со времениPAT1
происходит и до следующегоPAT2
виден. Это также напечатает строки из последнего матчаPAT1
до конца файла.
Печать строк между PAT1 и PAT2 - не включая PAT1 и PAT2
$ awk '/PAT1/{flag=1; next} /PAT2/{flag=0} flag' file
3 - first block
4
7 - second block
10 - third block
Это использует next
пропустить строку, содержащую PAT1
во избежание печати.
Этот призыв к next
можно сбросить, перетасовав блоки: awk '/PAT2/{flag=0} flag; /PAT1/{flag=1}' file
,
Печать строк между PAT1 и PAT2 - включая PAT1
$ awk '/PAT1/{flag=1} /PAT2/{flag=0} flag' file
PAT1
3 - first block
4
PAT1
7 - second block
PAT1
10 - third block
Поместив flag
в самом конце он запускает действие, заданное для PAT1 или PAT2: печатать в PAT1, а не печатать в PAT2.
Печать строк между PAT1 и PAT2 - включая PAT2
$ awk 'flag; /PAT1/{flag=1} /PAT2/{flag=0}' file
3 - first block
4
PAT2
7 - second block
PAT2
10 - third block
Поместив flag
в самом начале он запускает действие, которое было установлено ранее, и, следовательно, печатает закрывающий шаблон, но не начальный.
Вывести строки между PAT1 и PAT2 - исключая строки от последнего PAT1 до конца файла, если не найдено другого PAT2
Это основано на решении Эд Мортон.
awk 'flag{
if (/PAT2/)
{printf "%s", buf; flag=0; buf=""}
else
buf = buf $0 ORS
}
/PAT1/ {flag=1}' file
Как однострочник:
$ awk 'flag{ if (/PAT2/){printf "%s", buf; flag=0; buf=""} else buf = buf $0 ORS}; /PAT1/{flag=1}' file
3 - first block
4
7 - second block
# note the lack of third block, since no other PAT2 happens after it
Это сохраняет все выбранные строки в буфере, который заполняется с момента обнаружения PAT1. Затем он продолжает заполняться следующими строками, пока не будет найден PAT2. В этот момент он печатает сохраненный контент и очищает буфер.
Как насчет классики sed
решение?
Печать строк между PAT1 и PAT2
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' file
или даже (спасибо Sundeep):
sed -n '/PAT1/,/PAT2/{//!p}'
Вышесказанное исключает границы диапазона.
Печать строк между PAT1 и PAT2 - включая PAT1 и PAT2
Следующее будет включать границы диапазона, что еще проще:
sed -n '/PAT1/,/PAT2/p' file
Печать строк между PAT1 и PAT2 - включая PAT1
Следующее включает только начало диапазона:
sed -n '/PAT1/,/PAT2/{/PAT2/!p}' file
Печать строк между PAT1 и PAT2 - включая PAT2
Следующее включает только конец диапазона:
sed -n '/PAT1/,/PAT2/{/PAT1/!p}' file
С помощью grep
с помощью PCRE (где доступно) для печати маркеров и линий между маркерами:
$ grep -Pzo "(?s)(PAT1(.*?)(PAT2|\Z))" file
PAT1
3 - first block
4
PAT2
PAT1
7 - second block
PAT2
PAT1
10 - third block
-P
perl-regexp, PCRE. Не во всехgrep
варианты-z
Обрабатывать ввод как набор строк, каждая из которых заканчивается нулевым байтом вместо новой строки-o
только для печати(?s)
DotAll, т.е. точка находит и новые строки(.*?)
Неживая находка\Z
Совпадение только в конце строки или до новой строки в конце
Печать строк между маркерами, исключая конечный маркер:
$ grep -Pzo "(?s)(PAT1(.*?)(?=(\nPAT2|\Z)))" file
PAT1
3 - first block
4
PAT1
7 - second block
PAT1
10 - third block
(.*?)(?=(\nPAT2|\Z))
Неживая находка с нетерпением для\nPAT2
а также\Z
Печать строк между маркерами, исключая маркеры:
$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(?=(\nPAT2|\Z)))" file
3 - first block
4
7 - second block
10 - third block
(?<=PAT1\n)
позитивный взгляд заPAT1\n
Печать строк между маркерами, исключая стартовый маркер:
$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(PAT2|\Z))" file
3 - first block
4
PAT2
7 - second block
PAT2
10 - third block
Для полноты, вот решение Perl:
Печать строк между PAT1 и PAT2 - включая PAT1 и PAT2
perl -ne '/PAT1/../PAT2/ and print' FILE
или же:
perl -ne 'print if /PAT1/../PAT2/' FILE
Вывести строки между PAT1 и PAT2 - исключить PAT1 и PAT2
perl -ne '/PAT1/../PAT2/ and !/PAT1/ and !/PAT2/ and print' FILE
или же:
perl -ne 'if (/PAT1/../PAT2/) {print unless /PAT1/ or /PAT2/}' FILE
Вывести строки между PAT1 и PAT2 - исключить только PAT1
perl -ne '/PAT1/../PAT2/ and !/PAT1/ and print' FILE
Вывести строки между PAT1 и PAT2 - исключить только PAT2
perl -ne '/PAT1/../PAT2/ and !/PAT2/ and print' FILE
Смотрите также:
- Раздел оператора диапазона в
perldoc perlop
больше на/PAT1/../PAT2/
грамматика:
Оператор дальности
... В скалярном контексте ".." возвращает логическое значение. Оператор является бистабильным, как триггер, и эмулирует оператор диапазона строк (запятая) sed, awk и различных редакторов.
Для
-n
вариант, см.perldoc perlrun
, что заставляет Perl вести себя какsed -n
,Perl Cookbook, 6.8 для подробного обсуждения выделения ряда строк.
Вот другой подход
Включить оба шаблона (по умолчанию)
$ awk '/PAT1/,/PAT2/' file
PAT1
3 - first block
4
PAT2
PAT1
7 - second block
PAT2
PAT1
10 - third block
Маскируйте оба образца
$ awk '/PAT1/,/PAT2/{if(/PAT2|PAT1/) next; print}' file
3 - first block
4
7 - second block
10 - third block
Шаблон начала маски
$ awk '/PAT1/,/PAT2/{if(/PAT1/) next; print}' file
3 - first block
4
PAT2
7 - second block
PAT2
10 - third block
Конец маски
$ awk '/PAT1/,/PAT2/{if(/PAT2/) next; print}' file
PAT1
3 - first block
4
PAT1
7 - second block
PAT1
10 - third block
В качестве альтернативы:
sed '/START/,/END/!d;//d'
При этом удаляются все строки, кроме тех, которые находятся между START и END, включая //d
удаляет строки START и END, так как //
заставляет sed использовать предыдущие шаблоны.
Это похоже на примечание к двум верхним ответам выше (awk и sed). Мне нужно было запустить его на большом количестве файлов, поэтому производительность была важна. Я поставил 2 ответа на нагрузочный тест 10000 раз:
sedTester.sh
for i in `seq 10000`;do sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' patternTester >> sedTesterOutput; done
awkTester.sh
for i in `seq 10000`;do awk '/PAT1/{flag=1; next} /PAT2/{flag=0} flag' patternTester >> awkTesterOutput; done
Вот результаты:
zsh sedTester.sh 11.89s user 39.63s system 81% cpu 1:02.96 total
zsh awkTester.sh 38.73s user 60.64s system 79% cpu 2:04.83 total
Кажется, что решения sed в два раза быстрее, чем решения awk (Mac OS).
Вы можете делать то, что вы хотите с sed
подавляя нормальную печать пространства шаблона с -n
, Например, чтобы включить шаблоны в результат, вы можете сделать:
$ sed -n '/PAT1/,/PAT2/p' filename
PAT1
3 - first block
4
PAT2
PAT1
7 - second block
PAT2
PAT1
10 - third block
Чтобы исключить шаблоны и просто напечатать, что находится между ними:
$ sed -n '/PAT1/,/PAT2/{/PAT1/{n};/PAT2/{d};p}' filename
3 - first block
4
7 - second block
10 - third block
Который ломается как
sed -n '/PAT1/,/PAT2/
- найти диапазон междуPAT1
а такжеPAT2
и подавить печать;/PAT1/{n};
- если это соответствуетPAT1
перейти кn
(следующая) строка;/PAT2/{d};
- если это соответствуетPAT2
удалить строку;p
- печатать все строки, попавшие в/PAT1/,/PAT2/
и не были пропущены или удалены.
Это может сработать для вас (GNU sed) при условии, что
PAT1
и
PAT2
находятся в отдельных строках:
sed -n '/PAT1/{:a:N;/PAT2/!ba;p}' file
Отключите неявную печать с помощью
-n
option и действовать как grep.
NB Все решения, использующие идиому диапазона ie
/PAT1/,/PAT2/ command
страдают от того же крайнего случая, когда
PAT1
существует, но
PAT2
нет и поэтому будет печатать из
PAT1
в конец файла.
Для полноты:
# PAT1 to PAT2 without PAT1
sed -n '/PAT1/{:a;N;/PAT2/!ba;s/^[^\n]*\n//p}' file
# PAT1 to PAT2 without PAT2
sed -n '/PAT1/{:a;N;/PAT2/!ba;s/\n[^\n]*$//p}' file
# PAT1 to PAT2 without PAT1 and PAT2
sed -n '/PAT1/{:a;N;/PAT2/!ba;/\n.*\n/!d;s/^[^\n]*\n\|\n[^\n]*$/gp}' file
NB В последнем решении
PAT1
и
PAT2
могут находиться в следующих друг за другом строках, и поэтому может возникнуть дополнительный крайний случай. ИМО оба удаляются и ничего не печатается.