Как печатать линии между двумя рисунками, включающими или исключающими (в 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 1
sed -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