Jak znaleźć wzorki w wielu wierszach za pomocą grep?

Chcę znaleźć pliki, które mają " abc " i " efg " w tej kolejności, a te dwa łańcuchy są w różnych liniach w tym pliku. Np: plik o treści:

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

Powinny być dopasowane.

 161
Author: codeforester, 2010-04-21

21 answers

Grep nie jest wystarczający do tej operacji.

Pcregrep , który znajduje się w większości współczesnych systemów Linux może być używany jako

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

Istnieje również nowszy pcre2grep . Oba są dostarczane przez PCRE project .

Pcre2grep jest dostępny dla Mac OS X poprzez porty Mac jako część portu pcre2:

% sudo port install pcre2 

I przez Homebrew jako:

% brew install pcre
 175
Author: ring bearer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-12 21:38:21

Nie jestem pewien, czy jest to możliwe z grep, ale sed sprawia, że jest to bardzo łatwe:

sed -e '/abc/,/efg/!d' [file-with-content]
 104
Author: LJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-13 20:01:52

Oto rozwiązanie inspirowane tą odpowiedzią :

  • Jeśli ' abc 'i' efg ' mogą być w tej samej linii:

    grep -zl 'abc.*efg' <your list of files>
    
  • Jeśli " abc " i " efg " muszą być w różnych wierszach:

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

Params:

  • -z traktuj wejście jako zbiór linii, z których każda zakończona jest bajtem zerowym, a nie znakiem nowej linii. tzn. grep zagraża wejściu jako jedna duża linia.

  • -l wypisuje nazwę KAŻDEGO pliku wejściowego, z którego wyjście Zwykle zostały wydrukowane.

  • (?s) Aktywuj PCRE_DOTALL, co oznacza, że '.'wyszukuje dowolny znak lub nowy wiersz.

 59
Author: atti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-14 23:50:26

Sed powinien wystarczyć, jak wspomniano powyżej,

Zamiast !d możesz po prostu użyć p do wydruku:

sed -n '/abc/,/efg/p' file
 28
Author: Kara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-28 22:21:05

W dużej mierze polegałem na pcregrep, ale z nowszym grep nie trzeba instalować pcregrep dla wielu jego funkcji. Wystarczy użyć grep -P.

W przykładzie pytania OP, myślę, że następujące opcje działają ładnie, z drugim najlepiej pasującym jak rozumiem pytanie:

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

Skopiowałem tekst jako /tmp / test1 i usunąłem 'g' i zapisałem jako / tmp / test2. Oto wyjście pokazujące, że pierwszy pokazuje dopasowany ciąg znaków, a drugi pokazuje tylko nazwę pliku (typowy -o pokazuje dopasowanie, a typowe-l pokazuje tylko nazwę pliku). Zauważ, że " z "jest konieczne dla multiline i" (.|\n) "oznacza dopasowanie albo" cokolwiek innego niż newline "albo" newline " - tzn. cokolwiek:

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

Aby sprawdzić, czy Twoja wersja jest wystarczająco Nowa, Uruchom man grep i sprawdź, czy coś podobnego pojawia się w pobliżu góry:

   -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.

Pochodzi z GNU grep 2.10.

 12
Author: sage,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-29 15:27:51

Można to łatwo zrobić, używając tr, Aby zastąpić nowe linie innym znakiem:

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

Tutaj używam znaku alarmu, \a (ASCII 7)zamiast nowej linii. To prawie nigdy nie znajduje się w Twoim tekście, a {[3] } może dopasować go do ., lub dopasować konkretnie do \a.

 9
Author: g.rocket,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-09 00:41:57

Możesz to zrobić bardzo łatwo, jeśli możesz użyć Perla.

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

Można to zrobić za pomocą jednego wyrażenia regularnego, ale wiąże się to z zapisaniem całej zawartości pliku w jeden ciąg znaków, co może zająć zbyt dużo pamięci przy dużych plikach. Dla kompletności, oto ta metoda:

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
 6
Author: sundar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-17 16:51:33

Nie wiem jak bym to zrobił z grepem, ale zrobiłbym coś takiego z awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo
Musisz jednak uważać, jak to robisz. Czy chcesz, aby Wyrażenie regularne pasowało do podłańcucha lub całego słowa? Dodaj znaczniki \w odpowiednio. Ponadto, chociaż jest to ściśle zgodne z tym, jak podałeś przykład, to nie działa, gdy abc pojawia się po raz drugi po efg. Jeśli chcesz się tym zająć, dodaj if odpowiednio w /abc / case itp.
 5
Author: frankc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-21 20:25:35

Awk one-liner:

awk '/abc/,/efg/' [file-with-content]
 4
Author: Swynndla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-27 16:29:26

Kilka dni temu opublikowałem alternatywę grep, która obsługuje to bezpośrednio, albo poprzez dopasowanie wieloliniowe, albo za pomocą warunków - mam nadzieję, że jest przydatna dla niektórych osób szukających tutaj. Tak wyglądałyby polecenia dla przykładu:

Multiline: sift -lm 'abc.*efg' testfile
Warunki: sift -l 'abc' testfile --followed-by 'efg'

Można również określić ,że ' efg 'musi podążać za 'abc' w określonej liczbie wierszy:
sift -l 'abc' testfile --followed-within 5:'efg'

Więcej informacji znajdziesz na stronie sift-tool.org .

 3
Author: svent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-06 00:08:54

Niestety nie możesz. z grep docs:

Grep przeszukuje nazwane pliki wejściowe (lub standardowe wejście, jeśli żadne pliki nie są nazwane, lub jeśli jako nazwę pliku podano pojedynczy myślnik-minus ( - )) w poszukiwaniu linii zawierających dopasowanie do podanego wzorca.

 2
Author: Kaleb Pederson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-21 20:24:15

Podczas gdy opcja sed jest najprostsza i najłatwiejsza, jednoliniowy LJ nie jest niestety najbardziej przenośny. Ci, którzy utknęli z wersją C Shell, będą musieli uciec z grzywki:

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

To niestety nie działa w bash et al.

 2
Author: bug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-27 16:57:36

Jeśli chcesz używać kontekstów, możesz to osiągnąć wpisując

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

Wyświetli wszystko pomiędzy "abc" i "efg", o ile znajdują się one w odległości 500 linii od siebie.

 2
Author: agouge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-10 15:48:58

Jeśli potrzebujesz, aby oba słowa były blisko siebie, na przykład nie więcej niż 3 linie, możesz to zrobić:

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

Ten sam przykład, ale filtrowanie tylko *.pliki txt:

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

A także możesz zastąpić grep poleceniem egrep Jeśli chcesz również znaleźć z wyrażeniami regularnymi.

 2
Author: Mariano Ruiz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-04 22:47:32
#!/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
Author: ghostdog74,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-22 00:02:25

Możesz użyć grepa okrywając, że nie jesteś zainteresowany sekwencją wzorca.

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

Przykład

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

grep -l znajdzie wszystkie pliki pasujące do pierwszego wzorca, a xargs wykona grep dla drugiego wzorca. Mam nadzieję, że to pomoże.

 1
Author: Balu Mohan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-24 17:07:01

With silver searcher :

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

Podobne do odpowiedzi okaziciela pierścienia, ale zamiast tego z ag. Prędkość zalety silver searcher może zabłysnąć tutaj.

 1
Author: Shwaydogg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-13 21:04:10

Jako alternatywa dla odpowiedzi Balu Mohan, możliwe jest wymuszenie kolejności wzorców za pomocą tylko grep, head i 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
Ta nie jest zbyt ładna. Sformatowany bardziej czytelnie:
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

Wyświetli nazwy wszystkich plików, w których "pattern2" pojawia się po "pattern1", lub gdzie oba pojawiają się w tej samej linii :

$ 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

Wyjaśnienie

  • tail -n +i - wypisuje wszystkie linie po i th, włącznie z
  • grep -n - prepend dopasowanie linii z ich numerami linii
  • head -n1 - wypisuje tylko pierwszy wiersz
  • cut -d : -f 1 - wyświetla pierwszą kolumnę cięcia używając : jako ogranicznika
  • 2>/dev/null - silence tail Wyjście błędu, które występuje, gdy wyrażenie $() zwróci puste
  • grep -q - cisza grep i zwracamy natychmiast, jeśli dopasowanie zostanie znalezione, ponieważ interesuje nas tylko kod wyjścia
 0
Author: Emil Lundberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-24 14:36:11

To też powinno zadziałać?!

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

$ARGV zawiera nazwę bieżącego pliku podczas odczytu z przeszukiwania modyfikatora file_list /s przez nowy wiersz.

 0
Author: PS12,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-18 20:37:48

Filepattern {[4] } jest ważny, aby zapobiec sprawdzaniu katalogów. Oczywiście jakiś test może temu zapobiec.

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 

Wyszukuje maksymalnie 1 pasujące i zwraca (- N) numer linii. Jeśli znaleziono dopasowanie (test-N...) find the last match of efg (find all and take the last with tail - N 1).

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

Else continue.

Ponieważ wynik jest czymś w rodzaju 18:foofile.sh String alf="abc"; musimy odciąć od": "aż do końca linii.

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

Powinien zwrócić wynik dodatni, jeśli ostatni mecz drugiego wyrażenia minął pierwszy mecz pierwszego.

Następnie zgłaszamy nazwę pliku echo $f.

 0
Author: user unknown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-15 01:02:54

To powinno zadziałać:

cat FILE | egrep 'abc|efg'

Jeśli jest więcej niż jedno dopasowanie, możesz odfiltrować używając grep-v

 -3
Author: Guru,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-09 11:37:03