Подтвердить что ты не робот

Как найти шаблоны в нескольких строках с помощью grep?

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

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

Следует сопоставить.

4b9b3361

Ответ 1

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

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

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

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

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

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

% sudo port install pcre2 

и через Homebrew как:

% brew install pcre

или для pcre2

% brew install pcre2

Ответ 2

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

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

Ответ 3

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

  • если "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, что означает, что '.' находит любой символ или новую строку.

Ответ 4

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

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

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

Ответ 5

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

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

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

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

[email protected]:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
[email protected]:~$ 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.

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

awk one-liner:

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

Ответ 9

Я не знаю, как это сделать с помощью 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 и т.д.

Ответ 10

К сожалению, вы не можете. Из grep docs:

grep ищет именованные входные ФАЙЛЫ (или стандартный ввод, если имена файлов не указаны, или если в качестве имени файла указывается один дефис-минус (-)) для строк, содержащих совпадение с данным ОБРАЗЕЦ.

Ответ 11

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

Многострочный: 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.

Ответ 12

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

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

Это, к сожалению, не работает в bash и др.

Ответ 13

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

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

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

Ответ 14

Если вам нужно, чтобы оба слова были близки друг к другу, например, не более 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, если вы хотите также найти регулярные выражения.

Ответ 15

#!/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

Ответ 16

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

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

Пример

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

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

Ответ 17

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

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

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

Ответ 18

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

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

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

Ответ 19

В качестве альтернативы ответу Балу Мохана можно выполнить порядок шаблонов, используя только 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 - распечатать все строки после i th, включительно
  • grep -n - добавить строки соответствия с номерами строк
  • head -n1 - печатать только первую строку
  • cut -d : -f 1 - напечатайте первый столбец разреза, используя : в качестве разделителя
  • 2>/dev/null - тихий tail вывод ошибки, который возникает, если выражение $() возвращает пустое
  • grep -q - silence grep и немедленно вернуться, если совпадение найдено, поскольку нас интересует только код выхода

Ответ 20

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

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

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

Ответ 21

Файлпаттер *.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) номер строки. Если совпадение найдено (тест -n...) найти последнее соответствие efg (найти все и взять последнее с хвостом -n 1).

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

еще продолжайте.

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

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

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

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

Ответ 22

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

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

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

Ответ 23

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

cat FILE | egrep 'abc|efg'

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