Как печатать линии между двумя рисунками, включающими или исключающими (в sed, AWK или Perl)?
У меня есть файл, подобный следующему, и я хотел бы напечатать строки между двумя заданными шаблонами 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, но мне любопытно увидеть все возможные комбинации этого, включая или исключая шаблон.
Как я могу напечатать все линии между двумя узорами?
Ответы
Ответ 1
Печать строк между 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. В этом случае он печатает сохраненное содержимое и опустошает буфер.
Ответ 2
Как насчет классического решения sed
?
Печать строк между PAT1 и PAT2 - включая PAT1 и PAT2
sed -n '/PAT1/,/PAT2/p' FILE
Вывести строки между PAT1 и PAT2 - исключить PAT1 и PAT2
GNU sed sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
Любой sed 1sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE
или даже (спасибо Sundeep):
GNU sed sed -n '/PAT1/,/PAT2/{//!p}' FILE
Любой сед sed -n '/PAT1/,/PAT2/{//!p;}' FILE
Печать строк между PAT1 и PAT2 - включает PAT1, но не PAT2
Следующее включает только начало диапазона:
GNU sed sed -n '/PAT1/,/PAT2/{/PAT2/!p}' FILE
Любой сед sed -n '/PAT1/,/PAT2/{/PAT2/!p;}' FILE
Печать строк между PAT1 и PAT2 - включает PAT2, но не PAT1
Следующее включает только конец диапазона:
GNU sed sed -n '/PAT1/,/PAT2/{/PAT1/!p}' FILE
Любой сед sed -n '/PAT1/,/PAT2/{/PAT1/!p;}' FILE
1 примечание о BSD/Mac OS X sed
Команда вот так:
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
Издаст ошибку:
▶ sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
sed: 1: "/PAT1/,/PAT2/{/PAT1/!{/ ...": extra characters at the end of p command
По этой причине этот ответ был отредактирован, чтобы включить версии с одной строкой для BSD и GNU.
Ответ 3
Используя 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, т.е. точка находит новые строки также
-
(.*?)
nongreedy find
-
\Z
Соответствует только концу строки или перед новой строкой в конце
Печать строк между маркерами, исключая маркер конца:
$ grep -Pzo "(?s)(PAT1(.*?)(?=(\nPAT2|\Z)))" file
PAT1
3 - first block
4
PAT1
7 - second block
PAT1
10 - third block
-
(.*?)(?=(\nPAT2|\Z))
nongreedy найти с lookahead для \nPAT2
и \Z
Печать строк между маркерами без маркеров:
$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(?=(\nPAT2|\Z)))" file
3 - first block
4
7 - second block
10 - third block
-
(?<=PAT1\n)
положительный lookbehind для PAT1\n
Печать строк между маркерами, исключая маркер начала:
$ grep -Pzo "(?s)((?<=PAT1\n)(.*?)(PAT2|\Z))" file
3 - first block
4
PAT2
7 - second block
PAT2
10 - third block
Ответ 4
Вот еще один подход
Включить оба шаблона (по умолчанию)
$ 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
Ответ 5
Вы можете сделать то, что хотите, с помощью 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
delete;
-
p
- печатать все строки, которые попадали в /PAT1/,/PAT2/
, и не были пропущены или удалены.
Ответ 6
В качестве альтернативы:
sed '/START/,/END/!d;//d'
Это удаляет все строки, за исключением тех, которые находятся между и включая START и END, а затем //d
удаляет строки START и END, так как //
заставляет sed использовать предыдущие шаблоны.
Ответ 7
Для полноты, вот решение 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 для подробного обсуждения выделения ряда строк.
Ответ 8
Примечание о Mac OS X:
Замечание об использовании некоторых из этих однострочников sed в Mac OS X (и, возможно, в других вариантах BSD).
Команда вот так:
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
Выдаст ошибку:
▶ sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
sed: 1: "/PAT1/,/PAT2/{/PAT1/!{/ ...": extra characters at the end of p command
Похоже, что версия BSD хочет, чтобы строки были завершены, а точка с запятой требуется для размещения сценария в одну строку:
▶ sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE
3 - first block
4
7 - second block
10 - third block
Переход на GNU sed (brew install gnu-sed) также исправляет это:
▶ gsed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p}}' FILE
Если сомневаетесь, используйте точку с запятой, так как она работает как на BSD, так и на GNU sed.
@hek2mgl ответ для Mac OS X/BSD sed:
Печать строк между PAT1 и PAT2
Исключая границы диапазона:
sed -n '/PAT1/,/PAT2/{/PAT1/!{/PAT2/!p;};}' FILE
или же:
sed -n '/PAT1/,/PAT2/{//!p;}' FILE
Печать строк между 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