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
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 dopasowaniefoo
i zrób to, co znajduje się wewnątrz zawijasów do pasujących linii. Zastąpfoo
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ą dobar
. 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 etykietystart
wcześniej tak, aby następna linia była dołączana, o ile przestrzeń wzoru nie zawierabar
. -
/your_regex/p
drukuje przestrzeń wzorca, jeśli pasuje doyour_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 znalezionydef
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, żesecond_line_word
znajduje się w trzecim wierszu, to nie tylko brakuje ci pierwszego wiersza (z powodu drugagrep
), 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 zmiennaall
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
-
/foo/{
jest wywoływane, gdy wiersz zawiera „foo” -
n
zamień przestrzeń wzorca na następną linię, tj. słowo „tutaj” -
b gotoloop
gałąź na etykietę „gotoloop” -
:gotoloop
definiuje etykietę „gotoloop” -
/bar/!{
, jeśli wzorzec nie zawiera „baru” -
h
zamień przestrzeń przechowywania na wzorzec, aby słowo „tutaj” zostało zapisane w tej przestrzeni. -
b loop
rozgałęzia się do etykiety „pętla” -
:loop
definiuje etykietę „pętla” -
N
dołącza wzorzec do miejsca przechowywania.
Teraz miejsce na przechowywanie zawiera:
„tutaj”
„to” -
:gotoloop
Jesteśmy teraz w kroku 4 i wykonujemy pętlę aż do linii zawierającej „bar” -
/bar/
pętla jest zakończona, „bar” został znaleziony. s przestrzeń wzorca - 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
-
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.
grep
. Są ściśle powiązane, ale nie dups, IMO."grep"
sugerując czasownik ” do grep ” i najlepsze odpowiedzi, w tym zaakceptowane, nie ' nie używaj grep.