Як вибрати лінії між двома шаблонами маркерів, які можуть виникати кілька разів із 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 'ТАК перестаньте шкодити'

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

1
@hkasera awk '/abc/{flag=1}/mno/{flag=0}flag' fileмає зробити.
fedorqui 'ТАК перестаньте шкодити'

2
@EirNym - це дивний сценарій, який можна обробляти дуже різними способами: які рядки ви б хотіли надрукувати? Напевно awk 'flag; /PAT1/{flag=1; next} /PAT1/{flag=0}' file, зробить.
fedorqui 'ТАК перестаньте шкодити'

45

Використання sed:

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

В -nкошти опції не друкувати за замовчуванням.

Картина виглядає для рядків , що містять тільки abcпросто mno, а потім виконує дії в { ... }. Перша дія видаляє abcрядок; другий mnoрядок; і pдрукує решта рядків. Ви можете розслабити реджекси за потребою. Будь-які рядки за межами діапазону abc.. mnoпросто не друкуються.


Дякуємо за відповідь та за пояснення! :)
двадцять

@JonathanLeffler Чи можу я знати, яка мета використання-e
Kasun Siyambalapitiya

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

@JonathanLeffler Спасибі
Kasun Siyambalapitiya

Приємно! (Я віддаю перевагу sed over awk.) При використанні складних регулярних виразів було б непогано повторювати їх. Чи не можливо видалити перший / останній рядок "вибраного" діапазону? Або спочатку застосувати dдо всіх рядків до першого матчу, а потім ще dдо всіх рядків, починаючи з другого матчу?
hans_meine

18

Це може допомогти вам (GNU sed):

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

Видаліть усі рядки, за винятком рядків між рядками, що починаються abcіmno



Це круто. В {//!b}перешкоджає тому , щоб abcі mnoвід включення в вихідний, але я не можу зрозуміти, яким чином . Чи можете ви пояснити?
Брендан

1
@Brendan інструкція //!bчитає, якщо поточний рядок не є жодною з рядків, що не відповідають діапазону, перервіть і, отже, надрукуйте ці рядки, інакше всі інші рядки буде видалено.
потонг

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

гольфи на два персонажі краще, ніж ппотонги {//!b};d

Порожні косої риски вперед //означають: "повторно використовувати останній використаний регулярний вираз". і команда робить те саме, що і зрозуміліше:

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

Це здається POSIX :

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


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

@potong правда! Мені доведеться детальніше вивчити, чому працює перший. Дякую!
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

7

З посилань попередньої відповіді, той, що зробив це для мене, працює kshна Solaris, був таким:

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

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

2
За допомогою "1, / firstmatch / d" ви говорите "з першого рядка до першого виявлення" firstmatch ", видалити". Тоді як з "/ secondmatch /, $ d" ви кажете "від першого появи" secondmatch "до кінця файлу, видаліть". крапка з комою відокремлює дві команди, які виконуються послідовно.
FanDeLaU

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

Добре знати еквівалент perl, оскільки це досить гарна альтернатива як awk, так і sed.
ахан

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 ...

редагувати: рішення 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 'ТАК перестаньте шкодити'

2
Я не погоджуюся, тому що для порівняння відповідей у ​​нас повинні бути критерії. Лише в кількох є додатки SED.
Лео Леопольд Герц 준영

0

Я намагався використовувати awkдля друку рядків між двома візерунками, тоді як pattern2 також відповідає pattern1 . І рядок pattern1 також слід надрукувати.

наприклад джерело

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

повинен мати вихід

package BBB
ddd
eee

Там, де pattern1 є package BBB, pattern2 є 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.