Как найти шаблоны по нескольким строкам, используя grep? - PullRequest
184 голосов
/ 21 апреля 2010

Я хочу найти файлы с «abc» И «efg» в этом порядке, и эти две строки находятся в разных строках в этом файле.Например: файл с содержанием:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

Должно совпадать.

Ответы [ 23 ]

194 голосов
/ 22 апреля 2010

Grep недостаточно для этой операции.

pcregrep , встречающийся в большинстве современных систем Linux, может использоваться как

pcregrep -M  'abc.*(\n|.)*efg' test.txt

где -M, --multiline позволяют шаблонам соответствовать более чем одной строке

Также имеется более новый pcre2grep . Оба предоставлены проектом PCRE .

pcre2grep доступен для Mac OS X через Порты Mac как часть порта pcre2:

% sudo port install pcre2 

и через Доморощенный как:

% brew install pcre

или для pcre2

% brew install pcre2
106 голосов
/ 22 апреля 2010

Я не уверен, возможно ли это с помощью grep, но sed делает это очень просто:

sed -e '/abc/,/efg/!d' [file-with-content]
72 голосов
/ 07 августа 2014

Вот решение, вдохновленное этим ответом :

  • , если 'abc' и 'efg' могут быть в одной строке:

    grep -zl 'abc.*efg' <your list of files>
    
  • , если 'abc' и 'efg' должны быть в разных строках:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>
    

Параметры:

  • -z Обрабатывать ввод как набор строк, каждая из которых заканчивается нулевым байтом вместо новой строки.т.е. grep угрожает вводу одной большой строкой.

  • -l выводит на печать имя каждого входного файла, из которого обычно выводился бы вывод.

  • (?s) активировать PCRE_DOTALL, что означает, что '.'находит любой символ или символ новой строки.

30 голосов
/ 14 августа 2014

sed должно быть достаточно, как указано выше в виде плаката LJ,

вместо! D, вы можете просто использовать p для печати:

sed -n '/abc/,/efg/p' file
12 голосов
/ 29 октября 2015

Я сильно полагался на pcregrep, но с новым grep вам не нужно устанавливать pcregrep для многих его функций. Просто используйте grep -P.

В примере с вопросом OP, я думаю, что следующие опции работают хорошо, со вторым лучшим соответствием, как я понимаю вопрос:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

Я скопировал текст как / tmp / test1, удалил «g» и сохранил как / tmp / test2. Вот выходные данные, показывающие, что первый показывает совпадающую строку, а второй показывает только имя файла (типично -o - показать совпадение, а типичное -l - показать только имя файла). Обратите внимание, что 'z' необходимо для многострочного, а '(. | \ N)' означает совпадение либо с "чем-либо, кроме newline", либо с "newline" - т.е. что угодно:

user@host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

Чтобы определить, является ли ваша версия достаточно новой, запустите man grep и посмотрите, появляется ли что-то похожее на это в верхней части:

   -P, --perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

Это из GNU grep 2.10.

10 голосов
/ 09 июля 2016

Это можно легко сделать, сначала используя tr, чтобы заменить символы новой строки каким-либо другим символом:

tr '\n' '\a' | grep -o 'abc.*def' | tr '\a' '\n'

Здесь я использую символ будильника \a (ASCII 7) вместо новой строки. Это почти никогда не встречается в вашем тексте, и grep может сопоставить его с . или специально сопоставить с \a.

6 голосов
/ 22 апреля 2010

Вы можете сделать это очень легко, если вы можете использовать Perl.

perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

Вы также можете сделать это с помощью одного регулярного выражения, но для этого нужно собрать все содержимое файла в одну строку, что может привести к тому, что большие файлы будут занимать слишком много памяти. Для полноты вот такой метод:

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
6 голосов
/ 17 января 2013

awk однострочный:

awk '/abc/,/efg/' [file-with-content]
5 голосов
/ 22 апреля 2010

Я не знаю, как бы я это сделал с grep, но я бы сделал что-то подобное с awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

Вы должны быть осторожны, как вы это делаете, хотя. Вы хотите, чтобы регулярное выражение соответствовало подстроке или всему слову? добавьте теги \ w по мере необходимости. Кроме того, хотя это строго соответствует тому, как вы указали пример, оно не совсем работает, когда abc появляется во второй раз после efg. Если вы хотите справиться с этим, добавьте, если необходимо, в / abc / case и т. Д.

3 голосов
/ 06 февраля 2015

Несколько дней назад я выпустил альтернативу grep, которая поддерживает это напрямую, либо через многострочное сопоставление, либо используя условия - надеюсь, это будет полезно для некоторых людей, которые ищут здесь.Вот как будут выглядеть команды для примера:

Multiline: sift -lm 'abc.*efg' testfileУсловия: sift -l 'abc' testfile --followed-by 'efg'

Вы также можете указать, что 'efg' должно следовать за 'abc' в пределах определенного количества строк:sift -l 'abc' testfile --followed-within 5:'efg'

Более подробную информацию вы можете найти на sift-tool.org .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...