AWK: Uzyskaj dostęp do przechwyconej grupy ze wzoru linii

229

Jeśli mam polecenie awk

pattern { ... }

a wzorzec używa grupy przechwytywania, jak mogę uzyskać dostęp do łańcucha przechwyconego w bloku?

szaleństwo
źródło
Czasami (w prostych przypadkach) możliwe jest dostosowanie separatora pól ( FS) i wybranie, z którym chcesz dopasować $field. Wstępne formatowanie danych wejściowych również może pomóc.
Krzysztof Jabłoński,
1
Odpowiedź na duplikat jest lepsza .
Samuel Edwin Ward
2
Samuel Edwin Ward: To też fajna odpowiedź! Ale wymaga również gawk(ponieważ używa gensub).
rampion

Odpowiedzi:

176

To był spacer ścieżką pamięci ...

Dawno temu zastąpiłem awk Perlem.

Najwyraźniej silnik wyrażeń regularnych AWK nie przechwytuje jego grup.

możesz rozważyć użycie czegoś takiego:

perl -n -e'/test(\d+)/ && print $1'

flaga -n powoduje, że perl zapętla każdą linię, tak jak robi to awk.

Peter Tillemans
źródło
3
Najwyraźniej ktoś się nie zgadza. Ta strona internetowa pochodzi z 2005 roku: tek-tips.com/faqs.cfm?fid=5674 Potwierdza, że ​​nie można ponownie używać dopasowanych grup w awk.
Peter Tillemans
3
Wolę „perl -n -p -e ...” niż awk dla prawie wszystkich przypadków użycia, ponieważ jest on bardziej elastyczny, potężniejszy i ma bardziej rozsądną składnię.
Peter Tillemans
15
gawk! = awk. Są to różne narzędzia i gawknie są domyślnie dostępne w większości miejsc.
Oli
6
OP specjalnie poprosił o rozwiązanie awk, więc nie sądzę, że to odpowiedź.
Joppe
6
@Joppe nie możesz dać rozwiązania awk, jeśli nie ma rozwiązania. W wierszu 3 wyjaśniam, że AWK nie obsługuje przechwytywania grup i podałem alternatywę, którą PO najwyraźniej docenił, ponieważ odpowiedź została zaakceptowana. Jak mógłbym lepiej odpowiedzieć na to pytanie?
Peter Tillemans,
335

Z gawk możesz użyć matchfunkcji do przechwytywania grup w nawiasach.

gawk 'match($0, pattern, ary) {print ary[1]}' 

przykład:

echo "abcdef" | gawk 'match($0, /b(.*)e/, a) {print a[1]}' 

wyjścia cd.

Zwróć uwagę na szczególne zastosowanie gawk, który implementuje tę funkcję.

W przypadku przenośnej alternatywy możesz osiągnąć podobne wyniki za pomocą match()i substr.

przykład:

echo "abcdef" | awk 'match($0, /b[^e]*/) {print substr($0, RSTART+1, RLENGTH-1)}'

wyjścia cd.

Glenn Jackman
źródło
4
Tak, warianty gxxx mają wiele dodatkowych zalet i mocy GNU.
Peter Tillemans
Działa również w BusyBox awk.
MrMas
32

Potrzebuję tego cały czas, więc stworzyłem dla niego funkcję bash. Opiera się na odpowiedzi Glenna Jackmana.

Definicja

Dodaj to do swojego .bash_profile itp.

function regex { gawk 'match($0,/'$1'/, ary) {print ary['${2:-'0'}']}'; }

Stosowanie

Przechwyć wyrażenie regularne dla każdej linii w pliku

$ cat filename | regex '.*'

Przechwyć pierwszą grupę przechwytywania wyrażeń regularnych dla każdej linii w pliku

$ cat filename | regex '(.*)' 1
opsb
źródło
2
Czym różni się od używania grep -o?
bfontaine
@bfontaine Czy można grep -owysyłać przechwycone grupy?
Olle Härstedt,
1
@ OlleHärstedt Nie, nie można. Dotyczy tylko przypadku użycia, gdy nie masz grup przechwytywania. W takim przypadku robi się brzydko z łańcuchami grep -o.
bfontaine,
15

Możesz użyć GNU awk:

$ cat hta
RewriteCond %{HTTP_HOST} !^www\.mysite\.net$
RewriteRule (.*) http://www.mysite.net/$1 [R=301,L]

$ gawk 'match($0, /.*(http.*?)\$/, m) { print m[1]; }' < hta
http://www.mysite.net/
Isvara
źródło
12
+1. Ponadto, z każdym awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton
1
Ed Morton: powiedziałbym, że zasługuje na najwyższą odpowiedź. edit: uhm ... to drukuje RewriteRule (.*) http://www.mysite.net/$dla mnie, co jest czymś więcej niż podgrupą.
rampion
4

Możesz także symulować przechwytywanie w waniliowym awk, bez rozszerzeń. Nie jest to jednak intuicyjne:

krok 1. użyj gensub, aby otaczać dopasowania niektórymi znakami, które nie pojawiają się w ciągu. krok 2. Użyj podziału przeciwko postaci. krok 3. Każdy inny element w podzielonej tablicy to twoja grupa przechwytywania.

$ echo 'ab cb ad' | awk '{split (gensub (/ a ./, SUBSEP "&" SUBSEP, "g", 0 $), cap, SUBSEP); czapka z daszkiem [2] „|” czapka [4]; } ”
ab | ad
Ydrol
źródło
3
Jestem prawie pewien, że gensubjest to gawkspecyficzna funkcja. Co otrzymasz z awk, jeśli wpiszesz awk --version; -?). Powodzenia wszystkim.
shellter
6
Jestem całkowicie pewien, że gensub jest gawk-izmem, chociaż awy BusyBox też go ma. Ta odpowiedź może być również zaimplementowana przy użyciu gsub, jednak:echo 'ab cb ad' | awk '{gsub(/a./,SUBSEP"&"SUBSEP);split($0,cap,SUBSEP);print cap[2]"|"cap[4]}'
dubiousjim
3
gensub () jest rozszerzeniem gawk, instrukcja gawk wyraźnie to mówi. Inne warianty awk również mogą to zaimplementować, ale wciąż nie jest to POSIX. Spróbuj gawk --posix '{gsub (...)}', a będzie narzekać
MestreLion
2
@MestreLion, masz na myśli, że będzie narzekać gawk --posix '{gensub(...)}'.
dubiousjim
1
Pomimo tego, że myliłeś się co do tego, że POSIX awk ma tę gensubfunkcję, twój przykład zastosował się do bardzo ograniczonego scenariusza: cały wzorzec jest zgrupowany, nie może pasować do czegoś podobnego, key=(value)gdy chcę wyodrębnić tylko valueczęści.
Miau
2

Zmagałem się trochę z wymyśleniem funkcji bash, która otacza odpowiedź Petera Tillemansa, ale oto co wymyśliłem:

funkcja regex {perl -n -e "/ $ 1 / && printf \"% s \ n \ "," '$ 1'}

Okazało się, że działało to lepiej niż oparta na awk funkcja bash opsb dla następującego argumentu wyrażenia regularnego, ponieważ nie chcę, aby drukowano ms

'([0-9]*)ms$'
wytten
źródło
Wolę to rozwiązanie, ponieważ możesz zobaczyć części grupy, które ograniczają przechwytywanie, jednocześnie je pomijając. Czy jednak ktoś mógłby wyjaśnić, jak to działa? Nie mogę sprawić, by ta składnia perla działała poprawnie w BASH, ponieważ nie rozumiem jej zbyt dobrze - szczególnie podwójne / pojedyncze cudzysłowy wokół$1
Demis
Nie jest to coś, co zrobiłem wcześniej lub później, ale patrząc wstecz, to, co robi, to łączenie dwóch ciągów, pierwszy ciąg jest w podwójnych cudzysłowach (ten pierwszy ciąg zawiera osadzone podwójne cudzysłowy, które ucieka się odwrotnym ukośnikiem), a drugi ciąg jest w pojedynczych cudzysłowach . Następnie wynik tej konkatenacji jest dostarczany jako argument do perla -e. Musisz także wiedzieć, że pierwszy $ 1 (ten w cudzysłowie) jest zastąpiony pierwszym argumentem funkcji, podczas gdy drugi $ 1 (ten w cudzysłowie) pozostaje nietknięty. Zobacz ten przykład
wytten
Rozumiem, to ma teraz trochę więcej sensu. Gdzie więc w poleceniu perl jest definicja dopasowania / wyrażenia regularnego wyrażenia regularnego? Widzę, że napisałeś '([0-9]*)ms$'- czy jest podany jako argument (a ciąg inny argument)? A wynik perl -ejest wstawiany do printfpolecenia bash , aby zastąpić %s, prawda? Dzięki, mam nadzieję to wykorzystać.
Demis,
1
Jako jedyny argument funkcji regularnego wyrażenia regularnego przekazujesz wyrażenie regularne ujęte w pojedyncze cudzysłowy. Przykład
wytten