Як я можу “змащувати” візерунки через кілька ліній?


24

Здається, я зловживаю grep/ egrep.

Я намагався шукати рядки в декількох рядках і не міг знайти збіг, хоча я знаю, що те, що я шукаю, повинно відповідати. Спочатку я вважав, що мої реджекси помиляються, але я врешті прочитав, що ці інструменти працюють за рядком (також мої регекси були настільки банальними, що це не могло бути проблемою).

Отже, який інструмент можна було б використовувати для пошуку шаблонів у кількох рядках?



1
@CiroSantilli - Я не думаю, що цей Q і той, з яким ви пов’язані, - це дублікати. Інший Q запитує, як би ви виконали збігання міжрядкових візерунків (тобто який інструмент повинен / чи можу я використовувати для цього), тоді як цей запитує, як це зробити grep. Вони тісно пов’язані між собою, але не дуп, ІМО.
slm

@sim ці випадки важко вирішити: я можу зрозуміти вашу думку. Я думаю, що цей конкретний випадок кращий як дублікат, тому що користувач сказав, що "grep"пропонує дієслово "to grep", а головні відповіді, включаючи прийняті, не використовують grep.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Відповіді:


24

Ось такий, sedякий надасть вам grepподібну поведінку в декількох рядках:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

Як це працює

  • -n пригнічує поведінку за замовчуванням друку кожного рядка
  • /foo/{}доручає йому відповідати fooі робити те, що потрапляє всередину виворотів до відповідних ліній. Замініть fooпочаткову частину візерунка.
  • :start - це розгалужувальна мітка, яка допоможе нам продовжувати циклічно, поки ми не знайдемо кінець нашому регексу.
  • /bar/!{}буде виконувати те, що знаходиться в squigglies до ліній, які не відповідають bar. Замініть barзакінчувальну частину візерунка.
  • Nдодає наступний рядок до активного буфера ( sedназиває це пробілом шаблону)
  • b startбезумовно розгалужується на startстворену нами раніше мітку, щоб продовжувати додавати наступний рядок, доки простір шаблону не містить bar.
  • /your_regex/pдрукує простір шаблону, якщо він відповідає your_regex. Ви повинні замінити your_regexцілим виразом, який ви хочете зіставити в декількох рядках.

1
+1 Додавання цього в занадтолюбивий! Спасибі.
wmorrison365

Примітка. На MacOS це даєsed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Стен Джеймс

1
sed: unterminated {Помилка отримання
Nomaed

@Nomaed Shot у темряві тут, але чи трапляється ваш регекс із символами "{"? Якщо це так, вам потрібно буде відхилити їх від косу.
Джозеф Р.

1
@Nomaed Схоже, це стосується відмінностей між sedреалізаціями. Я намагався дотримуватися рекомендацій у цій відповіді, щоб зробити вищезазначений сценарій стандартом, але він сказав мені, що "старт" був невизначеною міткою. Тож я не впевнений, чи можна це зробити стандартним чином. Якщо ви все-таки керуєте цим, будь ласка, не соромтесь редагувати мою відповідь.
Йосиф Р.

19

Я зазвичай використовую інструмент, pcregrepякий називається, який можна встановити на більшій частині смаку Linux за допомогою yumабо apt.

Наприклад, наприклад.

Припустимо, якщо у вас є файл, названий testfileіз вмістом

abc blah
blah blah
def blah
blah blah

Ви можете виконати таку команду:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

зробити відповідність шаблонів у кількох рядках

Більше того, ви можете зробити те ж саме і з sed.

$ sed -e '/abc/,/def/!d' testfile

5

Ось простіший підхід із використанням Perl:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

або (оскільки JosephR взяв sedмаршрут , я безсоромно вкраду його пропозицію )

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

Пояснення

$f=join("",<>);: це зчитує весь файл і зберігає його вміст (нові рядки та всі) у змінну $f. Потім ми намагаємося зіставити foo\nbar.*\nта надрукуємо його, якщо воно відповідає (спеціальна змінна $&містить останній знайдений збіг). ///mНеобхідно , щоб зробити регулярний вираз матч через переведення рядків.

Встановлює -0роздільник запису вхідних даних. Встановлюючи це, 00активує режим «абзац», коли Perl використовуватиме послідовні нові рядки ( \n\n) як роздільник записів. У випадках, коли немає нових послідовних послідовностей, весь файл читається (базується) відразу.

Увага:

Як НЕ зробити це для великих файлів, вона буде завантажувати весь файл в пам'ять , і це може бути проблемою.


2

Один із способів зробити це - за допомогою Perl. наприклад, ось вміст файлу з назвою foo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

Тепер ось Perl, який буде відповідати будь-якій лінії, що починається з foo, а потім будь-якою лінією, що починається з bar:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl, розбитий:

  • while(<>){$all .= $_} Це завантажує весь стандартний вхід до змінної $all
  • while($all =~Хоча змінна allмає регулярний вираз ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mРегекс: foo на початку рядка, за ним будь-яка кількість символів, що не є новим рядком, після чого - новий рядок, одразу ж «bar», а решта рядка з рядком у ньому. /mв кінці регулярного вираження означає "збіг через кілька рядків"
  • print $1 Роздрукуйте частину регулярного вираження, яка була в дужках (у цьому випадку весь регулярний вираз)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Стерти перше збіг для регулярного виразу, щоб ми могли зіставити кілька випадків регулярного виразу у відповідному файлі

І вихід:

foo line 1
bar line 2
foo
bar line 6

3
Щойно сказав, що ваш Perl можна скоротити до більш ідіоматичного:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Джозеф Р.

2

Grep альтернативний просіювач підтримує багаторівневу відповідність (відмова: Я - автор).

Припустимо, testfileмістить:

<книга>
  <title> Lorem Ipsum </title>
  <опис> Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempor incididunt ut
  labore et dolore magna aliqua </description>
</book>


sift -m '<description>.*?</description>' (показати рядки, що містять опис)

Результат:

testfile: <опис> Lorem ipsum dolor sit amet, consectetur
testfile: adipiscing elit, sed do eiusmod tempor incididunt ut
testfile: labore et dolore magna aliqua </description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (витягнути і переформатувати опис)

Результат:

description = "Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempor incididunt ut
  labore et dolore magna aliqua "

1
Дуже приємний інструмент. Вітаємо! Спробуйте включити його в такі дистрибутиви, як Ubuntu.
Луренко

2

Просто звичайна grep, яка підтримує Perl-regexpпараметр P, зробить цю роботу.

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) називається модифікатором DOTALL, який робить крапку у вашому регулярному виразі не лише символами, але й розривами рядків.


Коли я спробую це рішення, вихід не закінчується на 'def', а йде до кінця файлу 'blah'
Баклі

можливо ваш греп не підтримує -Pваріант
Avinash Raj

1

Я вирішив цю для мене за допомогою grep та -A варіант з іншим grep.

grep first_line_word -A 1 testfile | grep second_line_word

Параметр -A 1 друкує 1 рядок після знайденого рядка. Звичайно, це залежить від комбінації файлів і слів. Але для мене це було найшвидшим і надійним рішенням.


псевдонім grepp = 'grep --color = auto -B10 -A20 -i', то кот деякий файл | грепп-бла | grepp foo | грепп-бар ... так, вони -A і -B дуже зручні ... у вас найкраща відповідь
Скотт Стенсленд

1

Припустимо, у нас є файл test.txt, що містить:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Можна використовувати наступний код:

sed -n '/foo/,/bar/p' test.txt

Для наступного виходу:

foo
here
is the
text
to keep between the 2 patterns
bar

1

Якщо ми хочемо отримати текст між двома шаблонами, виключаючи себе.

Припустимо, у нас є файл test.txt, що містить:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Можна використовувати наступний код:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

Для наступного виходу:

here
is the
text
to keep between the 2 patterns

Як це працює, давайте зробимо це покроково

  1. /foo/{ спрацьовує, коли рядок містить "foo"
  2. n замініть простір шаблону наступним рядком, тобто словом "тут"
  3. b gotoloop відділення до мітки "gotoloop"
  4. :gotoloop визначає мітку "gotoloop"
  5. /bar/!{ якщо шаблон не містить "бар"
  6. h замініть простір утримування шаблоном, так що "тут" зберігається у просторі утримування
  7. b loop відділення до мітки "петля"
  8. :loop визначає мітку "петля"
  9. N додає візерунок до місця утримування.
    Тепер пробіл утримує містить:
    "тут"
    "є"
  10. :gotoloop Зараз ми переходимо до кроку 4, і циклуємо, поки рядок не містить "бар"
  11. /bar/ цикл закінчений, "бар" знайдено, це простір шаблону
  12. g простір шаблону замінено на простір утримування, який містить усі рядки між "foo" та "bar", які збереглися під час основного циклу
  13. p Скопіюйте простір шаблону на стандартний вихід

Готово!


Молодці, +1. Зазвичай я уникаю використання цих команд, вводячи нові рядки в SOH і виконуючи звичайні команди sed, а потім замінювати нові рядки.
A.Danischewski
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.