Как выбрать строки между двумя шаблонами маркеров, которые могут встречаться несколько раз с awk / sed


119

Используя awkили sedкак я могу выбрать линии, которые встречаются между двумя разными шаблонами маркеров? Эти шаблоны могут быть отмечены несколькими разделами.

Например: предположим, что файл содержит:

abc
def1
ghi1
jkl1
mno
abc
def2
ghi2
jkl2
mno
pqr
stu

И начальный образец, abcа конечный образец - mno Итак, мне нужен вывод как:

def1
ghi1
jkl1
def2
ghi2
jkl2

Я использую sed, чтобы один раз сопоставить шаблон:

sed -e '1,/abc/d' -e '/mno/,$d' <FILE>

Есть ли способ sedили awk не делать это несколько раз до конца файла?

Ответы:


188

Используйте awkс флажком для запуска печати при необходимости:

$ awk '/abc/{flag=1;next}/mno/{flag=0}flag' file
def1
ghi1
jkl1
def2
ghi2
jkl2

Как это работает?

  • /abc/соответствует строкам, имеющим этот текст, а также /mno/делает.
  • /abc/{flag=1;next}устанавливает, flagкогда текст abcнайден. Затем он пропускает очередь.
  • /mno/{flag=0}сбрасывает, flagкогда текст mnoнайден.
  • Финал flag- это шаблон с действием по умолчанию print $0: если flagон равен 1, строка печатается.

Более подробное описание и примеры, а также случаи, когда узоры показаны или нет, см. В разделе Как выбрать линии между двумя узорами? ,


30
Если вы хотите напечатать все, что находится между узором и включительно, вы можете использовать awk '/abc/{a=1}/mno/{print;a=0}a' file.
scai

6
Да, @scai! или даже awk '/abc/{a=1} a; /mno/{a=0}' file- с этим, поставив aусловие перед, /mno/мы заставим его оценить строку как истинную (и распечатать ее) перед установкой a=0. Таким образом мы можем избежать написания print.
fedorqui `` SO, перестаньте причинять вред ''

12
@scai @fedorqui Для включения вывода шаблонов вы можете сделатьawk '/abc/,/mno/' file
Jotne

1
@hkasera awk '/abc/{flag=1}/mno/{flag=0}flag' fileдолжен сделать.
fedorqui 'SO stop harming'

2
@EirNym - это странный сценарий, с которым можно справиться разными способами: какие строки вы хотите напечатать? Наверное awk 'flag; /PAT1/{flag=1; next} /PAT1/{flag=0}' fileсделал бы.
fedorqui `` SO stop harming ''

45

Использование sed:

sed -n -e '/^abc$/,/^mno$/{ /^abc$/d; /^mno$/d; p; }'

Параметр -nозначает, что по умолчанию не печатать.

Шаблон ищет строки, содержащие только abcодно mno, а затем выполняет действия в { ... }. Первое действие удаляет abcстроку; вторая mnoлиния; и pпечатает оставшиеся строки. При необходимости вы можете расслабить регулярные выражения. Строки вне диапазона abc.. mnoпросто не печатаются.


Спасибо за ответ и за объяснение! :)
двай

@JonathanLeffler, могу я узнать, какова цель использования-e
Касун Сиямбалапития

1
@KasunSiyambalapitiya: В основном это означает, что мне нравится его использовать. Формально он указывает, что следующий аргумент - это (часть) скрипта, который sedдолжен выполняться. Если вы хотите или должны использовать несколько аргументов для включения всего сценария, вы должны использовать их -eперед каждым таким аргументом; в противном случае это необязательно (но явно).
Джонатан Леффлер,

@JonathanLeffler Спасибо
Kasun Siyambalapitiya

Ницца! (Я предпочитаю sed, а не awk.) При использовании сложных регулярных выражений было бы неплохо не повторять их. Разве нельзя удалить первую / последнюю строку «выделенного» диапазона? Или сначала применить dко всем строкам до первого совпадения, а затем еще dко всем строкам, начиная со второго совпадения?
hans_meine

18

Это может сработать для вас (GNU sed):

sed '/^abc$/,/^mno$/{//!b};d' file

Удалите все строки, кроме строк между началом abcиmno



Это круто. В {//!b}препятствует тому , чтобы abcи mnoот включения в выходной, но я не могу понять, каким образом . Могли бы вы объяснить?
Брендан

1
@Brendan инструкция //!bчитает, если текущая строка не является ни одной из строк, соответствующих диапазону, прервать и, следовательно, распечатать эти строки, иначе все остальные строки будут удалены.
Potong

13
sed '/^abc$/,/^mno$/!d;//d' file

играет в гольф на двух персонажей лучше, чем у ppotong {//!b};d

Пустые косые черты //означают: «повторно использовать последнее использованное регулярное выражение». и команда делает то же самое, что и более понятное:

sed '/^abc$/,/^mno$/!d;/^abc$/d;/^mno$/d' file

Это , как представляется, POSIX :

Если RE пусто (то есть шаблон не указан), sed должен вести себя так, как если бы был указан последний RE, использованный в последней примененной команде (либо как адрес, либо как часть замещающей команды).


1
Я думаю, что второе решение ни к чему не приведет, поскольку вторая команда также является диапазоном. Однако престижность для первого.
Potong

@potong правда! Мне нужно больше узнать, почему работает первый. Спасибо!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

7

Судя по ссылкам, приведенным в предыдущем ответе, я сделал это за меня, работая kshна Solaris, следующим образом:

sed '1,/firstmatch/d;/secondmatch/,$d'
  • 1,/firstmatch/d: со строки 1 до первого поиска firstmatch, удалите.
  • /secondmatch/,$d: удалить с первого появления secondmatchдо конца файла.
  • Точка с запятой разделяет две команды, которые выполняются последовательно.

Просто любопытно, почему ограничитель диапазона ( 1,) стоит раньше /firstmatch/? Полагаю, это тоже можно сформулировать '/firstmatch/1,d;/secondmatch,$d'?
Люк Дэвис

2
С помощью «1, / firstmatch / d» вы говорите «от строки 1 до того момента, когда вы впервые найдете« firstmatch », удалите». Принимая во внимание, что с «/ secondmatch /, $ d» вы говорите «от первого появления 'secondmatch' до конца файла, удалить». точка с запятой разделяет две команды, которые выполняются последовательно.
FanDeLaU

2
perl -lne 'print if((/abc/../mno/) && !(/abc/||/mno/))' your_file

Полезно знать эквивалент Perl, поскольку это довольно хорошая альтернатива как awk, так и sed.
akhan 08

2

что-то вроде этого работает для меня:

file.awk:

BEGIN {
    record=0
}

/^abc$/ {
    record=1
}

/^mno$/ {
    record=0;
    print "s="s;
    s=""
}

!/^abc|mno$/ {
    if (record==1) {
        s = s"\n"$0
    }   
}

используя: awk -f file.awk data...

edit: решение O_o fedorqui намного лучше / красивее, чем мое.


3
В GNU awk if (record=1)должен быть if (record==1), то есть двойным = - см. Операторы сравнения gawk
Джордж Хокинс,

2

Ответ Don_crissti от Показать только текст между двумя совпадающими шаблонами ?

firstmatch="abc"
secondmatch="cdf"
sed "/$firstmatch/,/$secondmatch/!d;//d" infile

что намного эффективнее, чем приложение AWK, см. здесь .


Я не думаю, что здесь имеет смысл связывать сравнения времени, поскольку требования к вопросам совершенно разные, отсюда и решения.
fedorqui 'SO stop harming'

2
Я не согласен, потому что у нас должны быть критерии для сравнения ответов. Лишь немногие имеют приложения SED.
Лео Леопольд Герц 준영

0

Я пытался использовать awkдля печати строк между двумя шаблонами, в то время как шаблон2 также соответствует шаблону1 . И строка pattern1 также должна быть напечатана.

например, источник

package AAA
aaa
bbb
ccc
package BBB
ddd
eee
package CCC
fff
ggg
hhh
iii
package DDD
jjj

должен иметь выход

package BBB
ddd
eee

Где package BBBшаблон1, шаблон2 package \w*. Обратите внимание, что CCCэто неизвестное значение, поэтому не может быть сопоставлено буквально.

В этом случае у меня не работают ни @scai, awk '/abc/{a=1}/mno/{print;a=0}a' fileни @fedorqui awk '/abc/{a=1} a; /mno/{a=0}' file.

Наконец, мне удалось решить эту проблему awk '/package BBB/{flag=1;print;next}/package \w*/{flag=0}flag' file, ха-ха

Немного больше усилий приведет к тому awk '/package BBB/{flag=1;print;next}flag;/package \w*/{flag=0}' file, чтобы напечатать также строку pattern2, то есть

package BBB
ddd
eee
package CCC
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.