Как найти шаблоны по нескольким строкам, используя grep?

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

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

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

30 ответов

Решение

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

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

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

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

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

% sudo port install pcre2 

и через Homebrew как:

% brew install pcre

или для pcre2

% brew install pcre2

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

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

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

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

Params:

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

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

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

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

sed -e '/abc/,/efg/!d' [file-with-content]

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

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

sed -n '/abc/,/efg/p' file

Я сильно полагался на 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.

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

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

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

awk one-liner:

awk '/abc/,/efg/' [file-with-content]

Если вы хотите использовать контексты, это можно сделать, набрав

grep -A 500 abc test.txt | grep -B 500 efg

Это отобразит все между "abc" и "efg", если они находятся в пределах 500 строк друг от друга.

Вы можете сделать это очень легко, если вы можете использовать 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

Я не знаю, как бы я это сделал с 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. Если вы хотите справиться с этим, добавьте if в случае необходимости в /abc/ case и т. Д.

Возможно с ripgrep:

      $ rg --multiline 'abc(\n|.)+?efg' test.txt
3:blah abc blah
4:blah abc blah
5:blah blah..
6:blah blah..
7:blah blah..
8:blah efg blah blah

Или другие заклинания.

Если хочешь .считать новой строкой:

      $ rg --multiline '(?s)abc.+?efg' test.txt
3:blah abc blah
4:blah abc blah
5:blah blah..
6:blah blah..
7:blah blah..
8:blah efg blah blah

Или эквивалентно наличию (?s)было бы rg --multiline --multiline-dotall

И чтобы ответить на исходный вопрос, где они должны быть на отдельных строках:

$ rg --multiline 'abc.*[\n](\n|.)*efg' test.txt

И если вы хотите, чтобы это было «не жадным», чтобы вы не просто получили первый abc с последним efg (разделите их на пары):

$ rg --multiline 'abc.*[\n](\n|.)*?efg' test.txt

https://til.hashrocket.com/posts/9zneks2cbv-multiline-matches-with-ripgrep-rg

Если вам нужно, чтобы оба слова были близко друг к другу, например, не более 3 строк, вы можете сделать это:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Тот же пример, но фильтрация только файлов *.txt:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

А также вы можете заменить grep команда с egrep Команда, если вы хотите, также найти с помощью регулярных выражений.

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

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

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

Вы можете найти больше информации на http://sift-tool.org/.

К сожалению, вы не можете. От grep документы:

grep ищет в именованных входных ФАЙЛАХ (или в стандартном вводе, если файлы не названы, или если в качестве имени файла указан один дефис-минус (-)) строки, содержащие соответствие данному ШАБЛОНУ.

Хотя опция sed самая простая и легкая, однострочная версия LJ, к сожалению, не самая портативная. Те, кто застрял с версией C Shell, должны избежать челки:

sed -e '/abc/,/efg/\!d' [file]

Это, к сожалению, не работает в Bash et al.

С серебряным искателем:

ag 'abc.*(\n|.)*efg'

похож на ответ на предъявителя кольца, но вместо этого используется ag. Скоростные преимущества серебряного искателя могли бы здесь проявиться.

Я использовал это, чтобы извлечь последовательность fasta из файла multi-fasta, используя опцию -P для grep:

grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta

-P для поисков на основе Perl -z для окончания строки в 0 байт, а не для новой строки char -o, чтобы просто захватить совпадения, поскольку grep возвращает всю строку (что в данном случае, так как вы сделали -z, это весь файл). Основой регулярного выражения является [^>] что означает "не больше, чем символ"

Вы можете использовать grep, если вы не заинтересованы в последовательности паттерна.

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

пример

grep -l "vector" *.cpp | xargs grep "map"

grep -l найдет все файлы, которые соответствуют первому шаблону, а xargs будет grep для второго шаблона. Надеюсь это поможет.

#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done

Если у вас есть некоторая оценка расстояния между двумя строками "abc" и "efg", которые вы ищете, вы можете использовать:

grep -r. -e 'abc' -A num1 -B num2 | grep 'efg'

Таким образом, первый grep вернет строку с "abc" плюс #num1 строки после него и #num2 строки после него, а второй grep просеет все эти строки, чтобы получить "efg". Тогда вы узнаете, в каких файлах они появляются вместе.

С ugrep, выпущенным несколько месяцев назад:

ugrep 'abc(\n|.)+?efg'

Этот инструмент оптимизирован по скорости. Он также совместим с GNU/BSD/PCRE-grep.

Обратите внимание, что мы должны использовать ленивое повторение +?, если вы не хотите сопоставить все строки с efg вместе до последнего efg в файле.

У вас есть как минимум пара вариантов -

  1. ДОТАЛЛ метод
  • используйте (?), чтобы ОБЪЕДИНИТЬ. символ для включения \ n
  • вы также можете использовать опережающий просмотр (?=\n) - не будет захвачен в совпадении

пример-текст:

      true
match me

false
match me one

false
match me two

true
match me three
third line!!
{BLANK_LINE}

команда:

      grep -Pozi '(?s)true.+?\n(?=\n)' example-text

-p для регулярных выражений Perl -o, чтобы соответствовать только шаблону, а не всей строке -z, чтобы разрешить разрывы строк -i делает регистронезависимым

выход:


Примечания:

      - +? makes modifier non-greedy so matches shortest string instead of largest (prevents from returning one match containing entire text)
  1. вы можете использовать ручной метод oldschool OG, используя \ n

команда:

      grep -Pozi 'true(.|\n)+?\n(?=\n)'

выход:

      true                                                  
match me                                              
true                                                  
match me three                                        
third line!!

Файл паттерна *.sh важно не допустить проверки каталогов. Конечно, некоторые тесты могут предотвратить это тоже.

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

grep -n -m1 abc $f 

ищет максимум 1 совпадение и возвращает (-n) номер белья. Если совпадение было найдено (test -n ...), найдите последнее совпадение с efg (найдите все и возьмите последнее с tail -n 1).

z=$( grep -n efg $f | tail -n 1)

еще продолжить.

Так как результат что-то вроде 18:foofile.sh String alf="abc"; нам нужно отрезать от ":" до конца строки.

((${z/:*/}-${a/:*/}))

Должен возвращать положительный результат, если последнее совпадение 2-го выражения прошло после первого совпадения первого.

Затем мы сообщаем имя файла echo $f,

В качестве альтернативы ответу Балу Мохана, можно применять порядок шаблонов, используя только grep, head а также tail:

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

Этот не очень красивый, хотя. Форматируется более наглядно:

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

Это напечатает имена всех файлов, где "pattern2" появляется после "pattern1"или где оба появляются в одной строке:

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

объяснение

  • tail -n +i - напечатать все строки после ith включительно
  • grep -n - предварительно сопоставлять строки с номерами строк
  • head -n1 - печатать только первый ряд
  • cut -d : -f 1 - распечатать первый вырезанный столбец, используя : как разделитель
  • 2>/dev/null - тишина tail вывод ошибки, если $() выражение возвращается пустым
  • grep -q - тишина grep и немедленно возвращаемся, если совпадение найдено, так как нас интересует только код выхода

Почему бы не что-нибудь простое вроде:

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

возвращает 0 или положительное целое число.

egrep -o (показывает только совпадения, трюк: несколько совпадений в одной строке создают многострочный вывод, как если бы они находились в разных строках)

  • grep -A1 abc (выведите abc и строку после него)

  • grep efg | wc -l (0-n количество строк efg, найденных после abc в той же или следующих строках, результат можно использовать в 'if")

  • grep можно изменить на egrep и т. д., если требуется сопоставление с образцом

Использование любого awk и чтение в память только по одной строке за раз:

      $ awk 'f && /efg/{print FILENAME; exit} /abc/{f=1}' file
file

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

Если вам нужен статус успешного/неуспешного выхода, как если бы вы получилиgrepзатем измените его на:

      awk 'f && /efg/{f++; exit} /abc/{f=1} END{ if (f==2) { print FILENAME; exit 0 } else exit 1 }' file

или если вы хотите обрабатывать несколько входных файлов и ваш awk поддерживаетnextfile:

      awk 'FNR==1{f=0} f && /efg/{print FILENAME; nextfile} /abc/{f=1}' file1 file2 ...

и т. д...

Это тоже должно работать?!

perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGV содержит имя текущего файла при чтении из file_list /s Модификатор ищет по новой строке.

Для рекурсивного поиска по всем файлам (по нескольким строкам в каждом файле) с присутствием ОБЕИХ строк (т.е. строка1 и строка2 в разных строках и обе находятся в одном файле):

grep -r -l 'string1' * > tmp; while read p; do grep -l 'string2' $p; done < tmp; rm tmp 

Для рекурсивного поиска по всем файлам (по нескольким строкам в каждом файле) при наличии ЛЮБОЙ строки (т.е. строка1 и строка2 в разных строках и либо в одном файле):

grep -r -l 'string1\|string2' * 

Я считаю, что следующее должно работать и имеет преимущество использования только расширенных регулярных выражений без необходимости установки дополнительного инструмента, такого какpcregrepесли у вас его еще нет или у вас нет-Pдоступна опция grep (например, macOS):

egrep -irzo “.*abc(.*\s.*){1,}.*efg.*" path_to_filenames

Предостережение: это имеет некоторые небольшие недостатки:

  • он найдет самый большой набор строк от первой до последней в каждом файле, если только...
  • есть несколько повторенийabc[вещи]efgшаблон в каждом файле.

Это должно работать:

cat FILE | egrep 'abc|efg'

Если найдено более одного совпадения, вы можете отфильтровать с помощью grep -v

Другие вопросы по тегам