Как выбрать линии между двумя узорами?

У меня есть файл, подобный следующему, и я хотел бы напечатать линии между двумя данными образцами 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могут находиться в следующих друг за другом строках, и поэтому может возникнуть дополнительный крайний случай. ИМО оба удаляются и ничего не печатается.

Другие вопросы по тегам