Як знайти візерунки в декількох рядках за допомогою grep?


208

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

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

Слід відповідати.


Відповіді:


225

Греп недостатній для цієї операції.

pcregrep, який є в більшості сучасних систем Linux, може використовуватися як

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

де -M, --multiline дозволяють моделі , щоб відповідати більш ніж однієї лінії

Також є новіший pcre2grep . Обидва передбачені проектом PCRE .

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

% sudo port install pcre2 

і через Homebrew як:

% brew install pcre

або для pcre2

% brew install pcre2

pcre2grep також доступний у Linux (Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE

11
@StevenLu -M, --multiline- Дозвольте шаблонам відповідати більше ніж один рядок.
— носія кільця

7
Зауважте, що. * (\ N |.) * Еквівалентно (\ n |.) *, А останній коротший. Більше того, у моїй системі "pcre_exec () помилка -8" виникає, коли я запускаю більш довгу версію. Тож спробуйте замість цього "abc (\ n |.) * Efg"!
— daveagp

6
Вам потрібно зробити вираз не жадібним у такому випадку:'abc.*(\n|.)*?efg'
— дзвінок

4
і ви можете пропустити перше .*-> 'abc(\n|.)*?efg'щоб зробити регулярний вираз коротшим (і бути педантичним)
— Michi

6
pcregrepце полегшує справи, але також grepбуде працювати. Наприклад, див stackoverflow.com/a/7167115/123695
— Michael MIOR

113

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

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

4
Файли не знаходять, він повертає відповідну частину з одного файлу
— shiggity

11
@Lj. будь ласка, можете пояснити цю команду? Мені знайоме sed, але якщо ніколи раніше не бачив такого виразу.
— Ентоні

1
@Anthony, це задокументовано на сторінці людини sed, під адресою. Важливо усвідомити, що / abc / & / efg / - це адреса.
— Кальмар

49
Я підозрюю, що ця відповідь була б корисною, якби вона мала трохи більше пояснень, і в такому випадку я б ще раз проголосувала за неї. Я знаю трохи sed, але недостатньо, щоб використати цю відповідь для створення значущого коду виходу після півгодини обертання. Порада: "RTFM" рідко отримує голоси на StackOverflow, як показує ваш попередній коментар.
— Майкл Шепер

25
Швидке пояснення на прикладі: sed '1,5d': видалити рядки між 1 і 5. sed '1,5! D': видалити рядки не між 1 і 5 (тобто зберегти рядки між), то замість числа ви можете пошук рядка з / шаблоном /. Дивіться також простіший нижче: sed -n '/ abc /, / efg / p' p призначений для друку, а прапор -n не відображає всіх рядків
— phil_w

86

Ось рішення, натхнене цією відповіддю :

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


@syntaxerror Ні, я думаю, що це лише малі регістри l. AFAIK немає -1варіанту числа .
— Sparhawk

Здається, ти маєш рацію, можливо, я зробив помилку на друку під час тестування. У будь-якому випадку вибачте, що проклали помилковий слід.
— syntaxerror

6
Це чудово. У мене просто одне питання щодо цього. Якщо -zпараметр вказує grep для обробки нових рядків, zero byte charactersто тоді для чого нам потрібен (?s)регекс? Якщо це вже не новий рядок, чи не повинен він .мати можливість прямого зіставлення?
— Durga Swaroop

1
-z (він же --null-data) і (? s) - саме те, що вам потрібно, щоб відповідати багаторядковій лінії зі стандартним грепом. Люди на MacOS, будь ласка, залишайте коментарі щодо наявності опцій -z або --null-data у ваших системах!
— Zeke Fast

4
-z точно не доступний на MacOS
— Ділан Ніколсон

33

sed має бути достатньо, як плакат LJ, зазначений вище,

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

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

16

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

На прикладі питання щодо ОП, я думаю, що наступні варіанти чудово працюють, і другий найкращий варіант відповідає тому, як я розумію питання:

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

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

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.


14

Це можна зробити легко, спершу використовуючи trдля заміни нових рядків на якийсь інший символ:

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

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


1
Це був мій підхід, але я використовував \0і, таким чином, потребував grep -aі узгодження \x00… Ви допомогли мені спростити! echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'заразecho $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'
— Чарлі Горічаназ

1
Використовуйте grep -o.
— киб

7

awk one-liner:

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

4
Це буде щасливо друкувати від abcкінця до кінця файлу, якщо у файлі відсутній шаблон закінчення або відсутній останній кінцевий шаблон. Ви можете це виправити, але це значно ускладнить сценарій.
— трійка

Як виключити /efg/з виводу?
— киб

6

Це можна зробити дуже легко, якщо ви можете використовувати 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

Знайдена друга відповідь була корисною для вилучення цілого багаторядкового блоку з сірниками на пару рядків - .*?для отримання мінімальної відповідності довелося використовувати не жадібну відповідність ( ).
— RichVel

5

Я не знаю, як би я це зробив з 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

На жаль, ви не можете. З grepдокументів:

grep шукає названі вхідні ФАЙЛИ (або стандартне введення, якщо жодні файли не названі, або якщо один імфічний мінус (-) задано як ім'я файлу) для рядків, що містять відповідність даному ПАТЕРНУ.


про щоgrep -Pz
— Наваро

3

Якщо ви готові використовувати контексти, цього можна досягти, набравши текст

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

Це покаже все між "abc" та "efg", якщо вони знаходяться в межах 500 рядків один від одного.


3

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


3

Кілька днів тому я випустив альтернативу 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 .


Я не думаю, що перший приклад sift -lm 'abc.*efg' testfileспрацьовує, тому що збіг жадібний і збиває всі рядки до останнього efgу файлі.
— доктор Алекс RE

2

Хоча варіант sed є найпростішим і найпростішим, одножильний LJ, на жаль, не самий портативний. Тим, хто застряг у версії C Shell, потрібно буде уникнути чуб:

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

Це, на жаль, не працює в bash та ін.


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

1

ви можете використовувати греп, якщо ви не зацікавлені в послідовності шаблону.

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

приклад

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

grep -lзнайде всі файли, які відповідають першому шаблону, і xargs буде "grep" для другого шаблону. Сподіваюсь, це допомагає.


1
Це ігнорує порядок "pattern1" та "pattern2", які відображаються у файлі, проте - OP спеціально вказує, що лише ті файли, де "pattern2" з'являється ПІСЛЯ "pattern1", повинні відповідати.
— Еміль Лундберг

1

З пошуком срібла :

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

подібний до відповіді носія дзвінка, але замість нього. Переваги швидкості срібного пошуку тут, можливо, світяться.


1
Це, здається, не працює. (echo abctest; echo efg)|ag 'abc.*(\n|.)*efg'не відповідає
— phiresky

1

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

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • P для пошуку на основі perl
  • z для створення кінця рядка в 0 байтах, а не знаку нового рядка
  • o просто зафіксувати відповідність, оскільки grep повертає весь рядок (що в цьому випадку, оскільки ви зробили -z - це весь файл).

Ядром регулярного вираження є те, [^>]що перекладається на "не більше символу"


0

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

Чи може хто-небудь пояснити, будь ласка &>? Я також його використовую, але ніде не бачив це документально підтверджено. До речі, чому ми маємо замовчувати греп таким чином? grep -qтеж не зробить трюк?
— синтаксис-помилка

1
&>вказує bash переспрямовувати як стандартний вихід, так і стандартну помилку, див. ПОВЕРНЕННЯ в посібнику з bash. Ви дуже праві в тому, що ми могли б так само добре зробити grep -q ...замість того grep ... &>/dev/null, щоб добре ловити!
— Еміль Лундберг

Так і думав. Заберуть біль від багато незручного набору тексту. Дякую за пояснення - тому я, мабуть, трохи пропустив у посібнику. (Подивився щось віддалене, пов’язане з цим деякий час тому.) --- Ви можете навіть розглянути можливість змінити це у своїй відповіді. :)
— syntaxerror

0

Це теж має працювати ?!

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

$ARGVмістить ім'я поточного файлу під час читання з file_list /sпошуку модифікаторів у новому рядку.


0

Файл *.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

The

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/:*/}))

Слід повернути позитивний результат, якщо останній збіг 2-го виразу минув перший матч першого.

Потім повідомляємо ім'я файлу echo $f.


0

Чому б не щось просте, як:

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 у тому ж або наступному рядку, результат може бути використаний у "якщо")

  • grep можна змінити на egrep тощо, якщо потрібне узгодження шаблону


0

Якщо у вас є деяка оцінка відстані між двома рядками 'abc' та 'efg', які ви шукаєте, ви можете використовувати:

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

Таким чином, перший grep поверне рядок з рядками 'abc' плюс # num1 після нього та # num2 рядками після нього, а другий grep просіять усі ці, щоб отримати 'efg'. Тоді ви дізнаєтеся, у яких файлах вони відображаються разом.


0

З ugrep вийшов кілька місяців тому:

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

Цей інструмент дуже оптимізований для швидкості. Він також сумісний з GNU / BSD / PCRE-grep.

Зауважте, що ми повинні використовувати ледаче повторення +?, якщо ви не хочете зіставити всі рядки разом efgразом до останнього efgу файлі.


-3

Це має працювати:

cat FILE | egrep 'abc|efg'

Якщо є більше одного матчу, ви можете відфільтрувати за допомогою grep -v


2
У той час як цей фрагмент коду вітається, і може забезпечити деяку допомогу, було б значно покращено , якщо вона була придбана пояснення про те , як і те, чому це вирішує проблему. Пам’ятайте, що ви відповідаєте на запитання читачів у майбутньому, а не лише про людину, яка зараз запитує! Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
— Toby Speight

1
Насправді це не пошук у кількох рядках , як зазначено в запитанні.
— n.st
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.