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?



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.

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

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.


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

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 ”

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.


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.


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

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 !


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.