Как я могу «grep» паттерны через несколько строк?

24

Кажется, я неправильно использую grep/ egrep.

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

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

Джим
источник
1
@CiroSantilli - я не думаю, что этот вопрос и тот, на который вы ссылаетесь, являются дубликатами. Другой вопрос спрашивает, как бы вы сделали многострочное сопоставление с образцом (то есть, какой инструмент я должен / могу использовать, чтобы сделать это), а этот спрашивает, как это сделать grep. Они тесно связаны, но не дураки, ИМО.
СЛМ
@ С этими делами трудно разобраться: я понимаю твою точку зрения. Я думаю, что этот конкретный случай лучше в качестве дубликата, потому что пользователь сказал, "grep"предлагая глагол "grep", а в верхних ответах, включая принятые, не используйте grep.
Сиро Сантилли 新疆 新疆 中心 法轮功 六四 事件

Ответы:

24

Вот sedтот, который даст вам grepподобное поведение в нескольких строках:

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

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

  • -n подавляет поведение по умолчанию при печати каждой строки
  • /foo/{}инструктирует его, чтобы соответствовать fooи делать то, что происходит внутри волнистых линий к соответствующим линиям. Заменить fooначальной частью шаблона.
  • :start является меткой ветвления, которая помогает нам продолжать цикл, пока мы не найдем конец нашему регулярному выражению.
  • /bar/!{}выполнит то, что в волнистых линиях, линиям, которые не совпадают 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 Выстрел в темноте, но есть ли в вашем регулярном выражении символы "{"? Если это так, вам нужно убежать от них.
Джозеф Р.
1
@Nomaed Кажется, что это связано с различиями между sedреализациями. Я попытался следовать рекомендациям в этом ответе, чтобы сделать приведенный выше сценарий совместимым со стандартом, но он сказал мне, что «start» - это неопределенная метка. Поэтому я не уверен, что это можно сделать стандартным образом. Если вам это удастся, пожалуйста, не стесняйтесь редактировать мой ответ.
Джозеф Р.
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
pradeepchhetri
источник
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», а остальная часть строки - с символом bar. /mв конце регулярного выражения означает "совпадать через несколько строк"
  • print $1 Выведите часть регулярного выражения, которая была в скобках (в данном случае, все регулярное выражение)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Удалите первое совпадение для регулярного выражения, чтобы мы могли сопоставить несколько случаев регулярного выражения в рассматриваемом файле.

И вывод:

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

Альтернативный sift grep поддерживает многострочное сопоставление (отказ от ответственности: я автор).

Предположим, testfileсодержит:

<Книга>
  <title> Lorem Ipsum </ title>
  <описание> Лорем Ипсум Долор Сит Амет, Заклинатель
  elit, sed do eiusmod tempor incididunt ut
  Labore et Dolore Magna Aliqua </ описание>
</ Книга>


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

Результат:

testfile: <описание> Lorem Ipsum Dolor Sit Amet, Concetetur
testfile: adipiscing elit, sed do eiusmod tempor incididunt ut
testfile: labore et dolore magna aliqua </ описание>


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

Результат:

description = "Lorem Ipsum Dolor Sit Amet, Заклинатель
  elit, sed do eiusmod tempor incididunt ut
  Labore et Dolore Magna Aliqua "
Svent
источник
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», а идет в конец файла «бла»
Бакли
может быть, ваш grep не поддерживает -Pопцию
Avinash Raj
1

Я решил это для меня, используя опцию grep и -A с другим grep.

grep first_line_word -A 1 testfile | grep second_line_word

Опция -A 1 печатает 1 строку после найденной строки. Конечно, это зависит от вашего файла и словосочетания. Но для меня это было самое быстрое и надежное решение.

Мансур
источник
alias grepp = 'grep --color = auto -B10 -A20 -i', затем cat somefile | Грепп бла | Грепп Фу | grepp bar ... да те -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 ветка с лейблом "готолуп"
  4. :gotoloop определяет метку "gotoloop"
  5. /bar/!{ если шаблон не содержит "бар"
  6. h замените пространство удержания на шаблон, так что «здесь» сохраняется в пространстве удержания
  7. b loop ветка с ярлыком "петля"
  8. :loop определяет метку "петля"
  9. N добавляет шаблон в пространство удержания.
    Теперь трюм содержит:
    "здесь"
    "это"
  10. :gotoloop Теперь мы находимся на шаге 4 и выполняем цикл, пока строка не будет содержать «bar»
  11. /bar/ цикл завершен, "бар" был найден, это образец пространства
  12. g Пространство шаблона заменяется пространством удержания, которое содержит все строки между «foo» и «bar», которые были сохранены во время основного цикла
  13. p скопировать пространство шаблона в стандартный вывод

Выполнено !

Николас Поллин-Бротель
источник
Молодец, +1. Я обычно избегаю использования этих команд, вводя строки в SOH и выполняя обычные команды sed, а затем заменяю строки.
А.Данищевский