Wygląda na to, że nadużywam grep / egrep.

Próbowałem wyszukać ciągi w wielu wierszach i nie mogłem znaleźć dopasowania, ponieważ 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ą na wiersz (również moje wyrażenia regularne były tak trywialne, że nie mogło to być problemem).

Więc jakiego narzędzia użyć do wyszukiwania wzorców w wielu wierszach?

Komentarze

  • możliwy duplikat Wielowierszowe dopasowanie do wzorca przy użyciu sed, awk lub grep
  • @CiroSantilli – Nie sądzę, aby ten Q i ten, z którym łączyłeś się, były duplikatami. Drugi Q pyta, jak ' d dopasować wieloliniowy wzorzec (tj. Jakie narzędzie powinno / czy mogę użyj do tego), podczas gdy ten pyta, jak to zrobić za pomocą grep. Są ściśle powiązane, ale nie dups, IMO.
  • @sim te przypadki są Trudno zdecydować: rozumiem twój punkt widzenia Myślę, że ten konkretny przypadek jest lepszy jako duplikat widzisz, że użytkownik powiedział "grep" sugerując czasownik ” do grep ” i najlepsze odpowiedzi, w tym zaakceptowane, nie ' nie używaj grep.
  • Nic nie wskazuje na to, że potrzebne jest tutaj wyrażenie regularne wieloliniowe. Rozważ pokazanie rzeczywistego przykładu z danymi wejściowymi i oczekiwanymi danymi wyjściowymi, a także swoim poprzednim wysiłkiem.

Odpowiedź

Tutaj „sa sed taki, który zapewni grep -podobne zachowanie w wielu wierszach:

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

Jak to działa

  • -n pomija domyślne zachowanie drukowania każdego wiersza
  • /foo/{} nakazuje dopasowanie foo i zrób to, co znajduje się wewnątrz zawijasów do pasujących linii. Zastąp foo początkową częścią wzorca.
  • :start to rozgałęziająca się etykieta, która pomaga nam zachować pętlę, dopóki nie znajdziemy końca naszego wyrażenia regularnego.
  • /bar/!{} wykona to, co znajduje się w zawijasach, aby wiersze, które nie pasują do bar. Zastąp końcową częścią wzorca.
  • N dodaje następną linię do aktywnego bufora (sed nazywa to przestrzenią wzorców)
  • b start bezwarunkowo rozgałęzia się do utworzonej przez nas etykiety start wcześniej tak, aby następna linia była dołączana, o ile przestrzeń wzoru nie zawiera bar.
  • /your_regex/p drukuje przestrzeń wzorca, jeśli pasuje do your_regex. Należy zastąpić your_regex całym wyrażeniem, które ma zostać dopasowane w wielu wierszach.

Komentarze

  • +1 Dodawanie tego do zestawu narzędzi! Dzięki.
  • Uwaga: w systemie MacOS powoduje to sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
  • Otrzymuję sed: unterminated { błąd
  • @Nomaed Shot in the dark here, ale czy zdarza się, że Twoje wyrażenie regularne zawiera jakieś ” {” znaki? Jeśli tak, ' będziesz musiał zmienić ich znaczenie za pomocą odwrotnego ukośnika.
  • @Nomaed Wygląda na to, że ma to związek z różnice między sed implementacjami. Próbowałem postępować zgodnie z zaleceniami zawartymi w tej odpowiedzi, aby powyższy skrypt był zgodny ze standardem, ale okazało się, że ” start ” jest niezdefiniowanym etykieta. Dlatego ' nie jestem pewien, czy można to zrobić w sposób zgodny ze standardami. Jeśli Ci się uda, edytuj moją odpowiedź.

Odpowiedź

Generalnie używam narzędzia o nazwie pcregrep, które można zainstalować w większości wersji Linuksa za pomocą yum lub apt.

Na przykład.

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

abc blah blah blah def blah blah blah 

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

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

, aby dopasować wzorce w wielu wierszach.

Ponadto możesz zrobić to samo z sed.

$ sed -e "/abc/,/def/!d" testfile 

Komentarze

  • ta sed sugestia pomija wiersz, w którym zostanie znaleziony def

Answer

Po prostu normalny grep, który obsługuje parametr Perl-regexp P, wykona tę pracę.

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

(?s) nazywany modyfikatorem DOTALL, który sprawia, że kropka w wyrażeniu regularnym dopasowuje nie tylko znaki, ale także końce linii.

Komentarze

  • Kiedy wypróbuję to rozwiązanie, dane wyjściowe nie kończą się na ' def ', ale przechodzi do końca pliku ' blah '
  • może twój grep nie obsługuje -P opcji
  • To była jedyna, która działała dla mnie – wypróbowałem wszystkie sed sugestie, ale nie ' nie posunęło się nawet do instalowania alternatyw grepa.
  • $ grep --version: grep (GNU grep) 3.1 w Windows Git Bash ma opcję -P, --perl-regexp, ale (?s) nie ' wydaje się, że tam działa. Nadal pokazuje tylko pierwszą linię. Ten sam wzorzec z tym samym ciągiem testowym działa w przypadku regex101.com . Czy istnieje alternatywa w Git Bash? sed? (sed (GNU sed) 4.8 tutaj)
  • Czy wiesz, jak dodać kontekst do wyniku? grep -1 nie ' nie działa tutaj.

Odpowiedź

Oto „prostsze podejście przy użyciu Perla:

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

lub (ponieważ JosephR wziął sed trasa , „bezwstydnie ukradnę jego sugestię )

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

### Wyjaśnienie

$f=join("",<>);: czyta cały plik i zapisuje jego zawartość (nowe linie i wszystko) do zmiennej $f. Następnie próbujemy dopasować foo\nbar.*\n i wydrukować, jeśli pasuje (specjalna zmienna $& przechowuje ostatnie znalezione dopasowanie). Element ///m jest potrzebny, aby wyrażenie regularne pasowało do nowych linii.

-0 ustawia separator rekordów wejściowych. Ustawienie tej opcji na 00 aktywuje „tryb akapitu”, 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 znaków nowej linii, cały plik jest odczytywany (wysypywany) naraz.

### Ostrzeżenie: nie rób tego dla dużych plików, załaduje się cały plik w pamięci i to może być problem.

Komentarze

  • Nie ' t wie dużo o Perlu, ale czy nie ' t musi to być my $f=join("",<>);, ściśle mówiąc?
  • @Sapphire_Brick jeśli jesteś w trybie ścisłym (use strict;). To ' to dobry nawyk, zwłaszcza podczas pisania większych skryptów, ale ' to przesada dla takiego małego jednowierszowego tekstu jeden.

Odpowiedź

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ącego wyniku:

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

Odpowiedź

Alternatywa grep sift obsługuje dopasowywanie wielowierszowe (zastrzeżenie: jestem autorem).

Załóżmy, że testfile zawiera:

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

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

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

Wynik:

description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" 

Komentarze

  • Bardzo ładne narzędzie. Gratulacje! Spróbuj uwzględnić go w dystrybucjach, takich jak Ubuntu.

Odpowiedź

Rozwiązałem ten problem za pomocą grep i – Opcja z innym grepem.

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 pliku i słowa. Ale dla mnie było to najszybsze i niezawodne rozwiązanie.

Komentarze

  • alias grepp = ' grep –color = auto -B10 -A20 -i ' następnie plik cat | grepp blah | grepp foo | grepp bar … tak, te -A i -B są bardzo przydatne …masz najlepszą odpowiedź
  • To nie jest ' t super deterministyczne i ignoruje cały wzorzec na korzyść po prostu uzyskania innej pojedynczej linii (tylko na podstawie jej bliskości do pierwszej linii). Lepiej jest ' powiedzieć programowi, aby posunął się tak daleko, jak to konieczne, aby dotrzeć do jakiegoś wzorca, ' re absolutnie pewne jest koniec tekstu, który ' próbujesz dopasować. Na przykład, jeśli testfile zostanie zaktualizowany w taki sposób, że second_line_word znajduje się w trzecim wierszu, to nie tylko brakuje ci pierwszego wiersza (z powodu druga grep), ale ' nie brakuje linii, która zaczęła się pojawiać między nimi.
  • To byłoby wystarczająco dobrym MO dla poleceń ad hoc, w których naprawdę potrzebujesz tylko jednej linii wyjściowej, którą już zrozumiałeś. Nie ' nie sądzę, że ' jest tym, o co chodzi w OP i prawdopodobnie możesz też po prostu skopiować / wkleić w tym momencie ze względu na jest to ad hoc.

Odpowiedź

Jednym ze sposobów jest użycie Perla. na przykład tutaj „jest zawartość pliku o nazwie foo:

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

A teraz Perl, który pasuje do dowolnej linii zaczynającej się od foo, po której następuje dowolny wiersz zaczynający się od bar:

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 .= $_} To wczytuje całe standardowe wejście do zmiennej $all
  • while($all =~ Podczas gdy zmienna all ma wyrażenie regularne …
  • /^(foo[^\n]*\nbar[^\n]*\n)/m Wyrażenie regularne: foo na początku linii, po której następuje dowolna liczba znaków niebędących znakami nowej linii, po których następuje znak nowej linii, zaraz po nim „bar” i reszta linii ze słupkiem. /m na końcu wyrażenia regularnego oznacza „dopasuj w wielu wierszach”
  • print $1 Wydrukuj część wyrażenia regularnego to było w nawiasach (w tym przypadku całe wyrażenie regularne)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Usuń pierwsze dopasowanie do wyrażenia regularnego, abyśmy mogli dopasować wiele przypadków wyrażenia regularnego w danym pliku

I wynik:

foo line 1 bar line 2 foo bar line 6 

Komentarze

  • Wpadłem tylko, żeby powiedzieć, że twój Perl można skrócić do bardziej idiomatycznego: perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo

Odpowiedź

Jeśli chcemy uzyskać tekst między dwoma wzorcami wykluczającymi się.

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ącego wyniku:

here is the text to keep between the 2 patterns 

Jak to działa, niech wykonaj to krok po kroku

  1. /foo/{ jest wywoływane, gdy wiersz zawiera „foo”
  2. n zamień przestrzeń wzorca na następną linię, tj. słowo „tutaj”
  3. b gotoloop gałąź na etykietę „gotoloop”
  4. :gotoloop definiuje etykietę „gotoloop”
  5. /bar/!{, jeśli wzorzec nie zawiera „baru”
  6. h zamień przestrzeń przechowywania na wzorzec, aby słowo „tutaj” zostało zapisane w tej przestrzeni.
  7. b loop rozgałęzia się do etykiety „pętla”
  8. :loop definiuje etykietę „pętla”
  9. N dołącza wzorzec do miejsca przechowywania.
    Teraz miejsce na przechowywanie zawiera:
    „tutaj”
    „to”
  10. :gotoloop Jesteśmy teraz w kroku 4 i wykonujemy pętlę aż do linii zawierającej „bar”
  11. /bar/ pętla jest zakończona, „bar” został znaleziony. s przestrzeń wzorca
  12. przestrzeń wzorców jest zastępowana spacją wstrzymania, która zawiera wszystkie linie między„ foo ”i„ bar ”, które zostały zapisane podczas głównej pętli
  13. p skopiuj przestrzeń wzorców na standardowe wyjście

Gotowe!

Komentarze

  • Dobra robota, +1. Zwykle unikam używania tych poleceń przez tr ' wstawianie nowych linii do SOH i wykonywanie normalnych poleceń sed, a następnie zastępowanie nowych linii.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *