Jak mogę sprawić, by „grep -zoP” wyświetlało każdy mecz osobno?
Mam plik na tym formularzu:
X/this is the first match/blabla
X-this is
the second match-
and here we have some fluff.
Chcę wyodrębnić wszystko, co pojawia się po znaku „X” i między tymi samymi znacznikami. Więc jeśli mam „X + dopasowanie +”, chcę otrzymać „dopasowanie”, ponieważ pojawia się po „X” i między znacznikiem „+”.
Więc dla podanego przykładowego pliku chciałbym otrzymać takie wyjście:
this is the first match
i wtedy
this is
the second match
Udało mi się uzyskać całą zawartość między X, a po nim znacznikiem, używając:
grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file
To jest:
grep -Po '(?<=X(.))(.|\n)+(?=\1)'
dopasowanie X, po którym następuje(something)
, zostaje przechwycone i dopasowane na końcu do(?=\1)
(oparłem kod na mojej odpowiedzi tutaj ).- Uwaga: używam
(.|\n)
do dopasowywania wszystkiego, w tym nowej linii, a także używam-z
w grep do dopasowywania nowych linii.
Więc to działa dobrze, jedyny problem pochodzi z wyświetlania wyjścia:
$ grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file
this is the first matchthis is
the second match
Jak widać, wszystkie dopasowania pojawiają się razem, przy czym „to jest pierwsze dopasowanie”, po którym następuje „to jest drugie dopasowanie” bez żadnego separatora. Wiem, że pochodzi to z użycia "-z", które traktuje cały plik jako zestaw linii, z których każda jest zakończona bajtem zerowym (znak ASCII NUL) zamiast nowej linii (cytując "man grep").
A więc: czy istnieje sposób na oddzielne uzyskanie wszystkich tych wyników?
Próbowałem też w GNU Awk:
awk 'match($0, /X(.)(\n|.*)\1/, a) {print a[1]}' file
ale nawet nie (\n|.*)
działało.
Odpowiedzi
Przypadek użycia jest dość problematyczny, ponieważ gdy tylko wydrukujesz dopasowania, tracisz informacje o tym, gdzie dokładnie był separator. Ale jeśli to akceptowalne, spróbuj podłączyć rurociąg xargs -r0
.
grep -zPo '(?<=X(.))(.|\n)+(?=\1)' file | xargs -r0
Te opcje są rozszerzeniami GNU, ale tak jest grep -z
i (w większości) grep -P
, więc być może jest to dopuszczalne.
awk
nie obsługuje odwołań wstecznych w ramach definicji wyrażenia regularnego.
Obejścia:
$ grep -zPo '(?s)(?<=X(.)).+(?=\1)' ip.txt | tr '\0' '\n' this is the first match this is the second match # with ripgrep, which supports multiline matching $ rg -NoUP '(?s)(?<=X(.)).+(?=\1)' ip.txt
this is the first match
this is
the second match
Można również użyć (?s)X(.)\K.+(?=\1)
zamiast (?s)(?<=X(.)).+(?=\1)
. Możesz także chcieć użyć niechciwego kwantyfikatora, aby uniknąć dopasowywania match+xyz+foobaz
danych wejściowychX+match+xyz+foobaz+
Z perl
$ perl -0777 -nE 'say $& while(/X(.)\K.+(?=\1)/sg)' ip.txt
this is the first match
this is
the second match
Oto kolejne rozwiązanie gnu-awk wykorzystujące RS
i RT
:
awk -v RS='X.' 'ch != "" && n=index($0, ch) { print substr($0, 1, n-1)
}
RT {
ch = substr(RT, 2, 1)
}' file
this is the first match
this is
the second match
Z GNU awk dla multi-char RS, RT i gensub () i bez konieczności wczytywania całego pliku do pamięci:
$ awk -v RS='X.' 'NR>1{print "<" gensub(end".*","",1) ">"} {end=substr(RT,2,1)}' file
<this is the first match>
<this is
the second match>
Oczywiście dodałem „<” i „>”, abyś mógł zobaczyć, gdzie zaczyna się / kończy każdy rekord wyjściowy.
Powyższy zakłada, że znak po X
nie jest bez powtarzania Regexp metachar (na przykład .
, ^
, [
, itd.), Tak YMMV
GNU grep -z
kończy rekordy wejścia / wyjścia znakami null (przydatne w połączeniu z innymi narzędziami, takimi jak sort -z
). pcregrep tego nie zrobi:
pcregrep -Mo2 '(?s)X(.)(.+?)\1' file
-onumber
używane zamiast lookarounds. ?
dodany leniwy kwantyfikator (na wypadek, gdyby \1
wystąpił później).