Jak mogę „grepować” wzory w wielu liniach?

24

Wydaje mi nadużywania grep/ egrep.

Próbowałem wyszukać ciągi w wielu wierszach i nie mogłem znaleźć dopasowania, podczas gdy wiem, że to, czego szukam, powinno pasować. Początkowo myślałem, że moje wyrażenia regularne są błędne, ale w końcu przeczytałem, że te narzędzia działają w wierszu (również moje wyrażenia regularne były tak trywialne, że nie mogło być problemu).

Którego narzędzia użyłbyś do wyszukiwania wzorców w wielu liniach?

Jim
źródło
możliwy duplikat dopasowania wzoru Multiline przy użyciu sed, awk lub grep
Ciro Santilli 15 改造 中心 法轮功 六四 事件
1
@CiroSantilli - nie sądzę, że to Q i ten, z którym się łączyłeś, są duplikatami. Drugie pytanie dotyczy sposobu, w jaki można wykonać dopasowanie do wzorca wieloliniowego (tj. Jakiego narzędzia powinienem / mogę użyć, aby to zrobić), podczas gdy ten pyta, jak to zrobić grep. Są ściśle powiązane, ale nie dupsami, IMO.
slm
@imim te przypadki są trudne do rozstrzygnięcia: rozumiem twój punkt widzenia. Myślę, że ten konkretny przypadek jest lepszy jako duplikat, ponieważ użytkownik powiedział, "grep"sugerując czasownik „grep”, a najlepsze odpowiedzi, w tym akceptowane, nie używaj grep.
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

24

Oto sedtaki, który da ci greppodobne zachowanie w wielu liniach:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

Jak to działa

  • -n pomija domyślne zachowanie drukowania każdej linii
  • /foo/{}instruuje, aby dopasować fooi zrobić to, co jest w zawijasach do pasujących linii. Zamień foona początkową część wzoru.
  • :start to rozgałęziona etykieta, która pomaga nam zapętlać, dopóki nie znajdziemy końca naszego wyrażenia regularnego.
  • /bar/!{}wykona to, co jest w zawijasach, do linii, które nie pasują bar. Zamień barna końcową część wzoru.
  • Ndołącza następny wiersz do aktywnego bufora ( sednazywa to przestrzenią wzorów)
  • b startbezwarunkowo rozgałęzi się do startetykiety, którą wcześniej utworzyliśmy, aby dodawać następny wiersz, dopóki przestrzeń wzorcowa nie będzie zawierała bar.
  • /your_regex/pdrukuje przestrzeń wzoru, jeśli pasuje your_regex. Powinieneś zastąpić your_regexcałym wyrażeniem, które chcesz dopasować w wielu liniach.
Joseph R.
źródło
1
+1 Dodanie tego do zestawu narzędzi! Dzięki.
wmorrison365
Uwaga: Na MacOS daje tosed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James
1
Uzyskiwanie sed: unterminated {błąd
Nomaed
@Nomaed Shot in the Dark tutaj, ale czy twoje wyrażenie regularne zawiera jakieś znaki „{”? Jeśli tak, musisz uciec przed nimi ukośnikiem odwrotnym.
Joseph R.
1
@Nomaed Wygląda na to, że ma to związek z różnicami między sedimplementacjami. Próbowałem zastosować się do zaleceń zawartych w tej odpowiedzi, aby powyższy skrypt był zgodny ze standardami, ale powiedział mi, że „start” był niezdefiniowaną etykietą. Nie jestem więc pewien, czy można to zrobić w sposób zgodny ze standardami. Jeśli dasz radę, zredaguj moją odpowiedź.
Joseph R.
19

Ogólnie używam narzędzia o nazwie, pcregrepktóre można zainstalować w większości wersji linuksowych za pomocą yumlub apt.

Na przykład

Załóżmy, że masz plik o nazwie testfilez zawartością

abc blah
blah blah
def blah
blah blah

Możesz uruchomić następujące polecenie:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

wykonać dopasowanie wzorca w wielu liniach.

Co więcej, możesz zrobić to samo sed.

$ sed -e '/abc/,/def/!d' testfile
pradeepchhetri
źródło
5

Oto prostsze podejście przy użyciu Perla:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

lub (od JosephR wziął sedtrasę , będę bezczelnie kraść jego sugestii )

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

Wyjaśnienie

$f=join("",<>);: odczytuje cały plik i zapisuje jego zawartość (znaki nowego wiersza i wszystkie) w zmiennej $f. Następnie próbujemy dopasować foo\nbar.*\ni wydrukować go, jeśli pasuje (specjalna zmienna $&zawiera ostatnie znalezione dopasowanie). Jest ///mto konieczne, aby wyrażenie regularne pasowało do nowych linii.

-0Ustawia separator rekordu wejściowego. Ustawienie tej opcji 00aktywuje „tryb akapitowy”, w którym Perl będzie używał kolejnych znaków nowej linii ( \n\n) jako separatora rekordów. W przypadkach, gdy nie ma kolejnych nowych wierszy, cały plik jest odczytywany (zawieszany) jednocześnie.

Ostrzeżenie:

Czy nie to zrobić dla dużych plików, to załadowanie całego pliku do pamięci i że może być problem.

terdon
źródło
2

Jednym ze sposobów na to jest Perl. np. oto zawartość pliku o nazwie foo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

Oto kilka Perli, które pasują do dowolnej linii rozpoczynającej się od foo, po której następuje dowolna linia rozpoczynająca się od paska:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl, w podziale:

  • while(<>){$all .= $_} Spowoduje to załadowanie całego standardowego wejścia do zmiennej $all
  • while($all =~Podczas gdy zmienna allma wyrażenie regularne ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mWyrażenie regularne: foo na początku wiersza, po którym następuje dowolna liczba znaków innych niż nowa linia, po której następuje nowa linia, po której następuje natychmiast „kreska”, a reszta linii zawiera kreskę. /mna końcu wyrażenia regularnego oznacza „dopasuj do wielu linii”
  • print $1 Wydrukuj część wyrażenia regularnego, która była w nawiasie (w tym przypadku całe wyrażenie regularne)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Usuń pierwsze dopasowanie wyrażenia regularnego, abyśmy mogli dopasować wiele przypadków wyrażenia regularnego w danym pliku

A wynik:

foo line 1
bar line 2
foo
bar line 6
Samiam
źródło
3
Po prostu wpadłem, żeby powiedzieć, że twój Perl może zostać skrócony do bardziej idiomatycznych:perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.
2

Alternatywny sift grep obsługuje dopasowanie wielu linii (zastrzeżenie: jestem autorem).

Załóżmy, że testfilezawiera:

<book>
  <title> Lorem Ipsum </title>
  <description> Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempor incididunt ut
  labore et dolore magna aliqua </description>
</book>


sift -m '<description>.*?</description>' (pokaż wiersze zawierające opis)

Wynik:

plik testowy: <opis> Lorem ipsum dolor sit amet, consectetur
plik testowy: adipiscing elit, sed do eiusmod tempor incididunt ut
plik testowy: labore et dolore magna aliqua </description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (wyodrębnij i ponownie sformatuj opis)

Wynik:

opis = "Lorem ipsum dolor sit amet, consectetur
  adipiscing elit, sed do eiusmod tempor incididunt ut
  labore et dolore magna aliqua ”
svent
źródło
1
Bardzo fajne narzędzie. Gratulacje! Spróbuj włączyć go do dystrybucji takich jak Ubuntu.
Lourenco,
2

Po prostu normalne grep, który obsługuje Perl-regexpparametr P, wykona tę pracę.

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) zwany modyfikatorem DOTALL, który sprawia, że ​​kropka w wyrażeniu regularnym jest dopasowywana nie tylko do znaków, ale także do podziałów linii.

Avinash Raj
źródło
Kiedy próbuję tego rozwiązania, dane wyjściowe nie kończą się na „def”, ale przechodzą na koniec pliku „bla”
buckley,
może twój grep nie obsługuje -Popcji
Avinash Raj
1

Rozwiązałem ten dla mnie, używając grep i opcji -A z innym grep.

grep first_line_word -A 1 testfile | grep second_line_word

Opcja -A 1 drukuje 1 linię po znalezionej linii. Oczywiście zależy to od kombinacji plików i słów. Ale dla mnie było to najszybsze i niezawodne rozwiązanie.

Manur
źródło
alias grepp = 'grep --color = auto -B10 -A20 -i' następnie cat somefile | grepp bla | grepp foo | pasek grepp ... tak, te -A i -B są bardzo przydatne ... masz najlepszą odpowiedź
Scott Stensland
1

Załóżmy, że mamy plik test.txt zawierający:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Można użyć następującego kodu:

sed -n '/foo/,/bar/p' test.txt

Dla następujących danych wyjściowych:

foo
here
is the
text
to keep between the 2 patterns
bar
Nicolas Pollin-Brotel
źródło
1

Jeśli chcemy uzyskać tekst między 2 wzorcami, wyłączając siebie.

Załóżmy, że mamy plik test.txt zawierający:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Można użyć następującego kodu:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

Dla następujących danych wyjściowych:

here
is the
text
to keep between the 2 patterns

Jak to działa, zróbmy to krok po kroku

  1. /foo/{ jest wyzwalany, gdy wiersz zawiera „foo”
  2. n zamień przestrzeń wzoru na następny wiersz, tzn. słowo „tutaj”
  3. b gotoloop gałąź do etykiety „gotoloop”
  4. :gotoloop definiuje etykietę „gotoloop”
  5. /bar/!{ jeśli wzór nie zawiera „paska”
  6. h zamień przestrzeń wstrzymania na wzór, aby „tutaj” zostało zapisane w przestrzeni wstrzymania
  7. b loop rozgałęzić się do etykiety „pętla”
  8. :loop definiuje etykietę „pętla”
  9. N dołącza wzór do przestrzeni wstrzymania.
    Teraz przytrzymaj miejsce zawiera:
    „tutaj”
    „jest”
  10. :gotoloop Jesteśmy teraz w kroku 4 i zapętlamy, aż linia będzie zawierać „słupek”
  11. /bar/ pętla jest zakończona, znaleziono „słupek”, to przestrzeń wzorcowa
  12. g przestrzeń wzoru jest zastępowana przestrzenią wstrzymania, która zawiera wszystkie linie między „foo” a „słupkiem”, które zostały zapisane podczas głównej pętli
  13. p skopiuj przestrzeń wzoru na standardowe wyjście

Gotowy !

Nicolas Pollin-Brotel
źródło
Dobra robota, +1. Zwykle unikam używania tych poleceń, tr'ingując nowe wiersze w SOH i wykonując normalne polecenia sed, a następnie zastępując nowe wiersze.
A.Danischewski