AWK: Zugriff auf die erfasste Gruppe über das Linienmuster


229

Wenn ich einen awk-Befehl habe

pattern { ... }

und Muster verwendet eine Erfassungsgruppe. Wie kann ich auf die im Block erfasste Zeichenfolge zugreifen?



Manchmal (in einfachen Fällen) ist es möglich, das Feldtrennzeichen ( FS) anzupassen und auszuwählen, was mit einem übereinstimmen soll $field. Das Vorformatieren der Eingabe könnte ebenfalls hilfreich sein.
Krzysztof Jabłoński

1
Es gibt eine bessere Antwort auf die doppelte Frage.
Samuel Edwin Ward

2
Samuel Edwin Ward: Das ist auch eine schöne Antwort! Aber es erfordert auch gawk(da es verwendet gensub).
Rampion

Antworten:


176

Das war ein Spaziergang in die Vergangenheit ...

Ich habe awk vor langer Zeit durch perl ersetzt.

Anscheinend erfasst die AWK-Engine für reguläre Ausdrücke ihre Gruppen nicht.

Sie könnten in Betracht ziehen, etwas zu verwenden wie:

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

Das Flag -n bewirkt, dass Perl wie awk jede Zeile durchläuft.


3
Anscheinend ist jemand anderer Meinung. Diese Webseite stammt aus dem Jahr 2005: tek-tips.com/faqs.cfm?fid=5674 Sie bestätigt, dass Sie übereinstimmende Gruppen in awk nicht wiederverwenden können.
Peter Tillemans

3
Ich bevorzuge 'perl -n -p -e ...' für fast alle Anwendungsfälle gegenüber awk, da es flexibler, leistungsfähiger und meiner Meinung nach eine vernünftigere Syntax hat.
Peter Tillemans

15
gawk! = awk. Sie sind verschiedene Tools und gawkan den meisten Orten standardmäßig nicht verfügbar.
Oli

6
Das OP hat speziell nach einer awk-Lösung gefragt, daher denke ich nicht, dass dies eine Antwort ist.
Joppe

6
@Joppe Sie können keine awk-Lösung geben, wenn es keine Lösung gibt. In Zeile 3 erkläre ich, dass AWK die Erfassung von Gruppen nicht unterstützt, und gab eine Alternative an, die das OP anscheinend zu schätzen wusste, weil diese Antwort akzeptiert wurde. Wie könnte ich diese Frage besser beantworten?
Peter Tillemans

335

Mit gawk können Sie die matchFunktion verwenden, um Gruppen in Klammern zu erfassen.

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

Beispiel:

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

Ausgänge cd.

Beachten Sie die spezifische Verwendung von Gawk, die die betreffende Funktion implementiert.

Für eine tragbare Alternative können Sie mit match()und ähnliche Ergebnisse erzielen substr.

Beispiel:

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

Ausgänge cd.


4
Ja, die gxxx-Varianten haben viele zusätzliche GNU-Güte und Leistung.
Peter Tillemans

Funktioniert auch in BusyBox awk.
MrMas

32

Dies ist etwas, das ich ständig brauche, also habe ich eine Bash-Funktion dafür erstellt. Es basiert auf der Antwort von Glenn Jackman.

Definition

Fügen Sie dies Ihrem .bash_profile usw. hinzu.

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

Verwendung

Erfassen Sie Regex für jede Zeile in der Datei

$ cat filename | regex '.*'

Erfassen Sie die erste Regex-Erfassungsgruppe für jede Zeile in der Datei

$ cat filename | regex '(.*)' 1

2
Wie unterscheidet es sich von der Verwendung grep -o?
Bfontaine

@bfontaine Könnten grep -oerfasste Gruppen ausgegeben werden?
Olle Härstedt

1
@ OlleHärstedt Nein, das konnte es nicht. Es deckt Ihren Anwendungsfall nur ab, wenn Sie keine Erfassungsgruppen haben. In diesem Fall wird es hässlich mit verketteten grep -o.
Bfontaine

15

Sie können GNU awk verwenden:

$ 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/

12
+1. Auch mit jedem awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton


1
Ed Morton: Das verdient eine Antwort auf höchster Ebene, würde ich sagen. edit: ähm ... das druckt RewriteRule (.*) http://www.mysite.net/$für mich, das ist mehr als die untergruppe.
Rampion


4

Sie können die Erfassung auch in Vanilla Awk ohne Erweiterungen simulieren. Es ist jedoch nicht intuitiv:

Schritt 1. Verwenden Sie gensub, um Übereinstimmungen mit einem Zeichen zu umgeben, das nicht in Ihrer Zeichenfolge enthalten ist. Schritt 2. Verwenden Sie Split für den Charakter. Schritt 3. Jedes andere Element im geteilten Array ist Ihre Erfassungsgruppe.

$ echo 'ab cb ad' | awk '{split (gensub (/ a ./, SUBSEP "&" SUBSEP, "g", $ 0), cap, SUBSEP); Druckkappe [2] "|" Kappe [4]; } '
ab | ad

3
Ich bin mir fast sicher, dass gensubdas ein istgawk bestimmte Funktion ist. Was bekommen Sie von Ihrem awk, wenn Sie tippen awk --version; -?). Viel Glück für jeden.
Shellter

6
Ich bin mir völlig sicher, dass Gensub ein Gawk-Ismus ist, obwohl BusyBox Awk es auch hat. Diese Antwort könnte jedoch auch mit gsub implementiert werden:echo 'ab cb ad' | awk '{gsub(/a./,SUBSEP"&"SUBSEP);split($0,cap,SUBSEP);print cap[2]"|"cap[4]}'
dubiousjim

3
gensub () ist eine gawk-Erweiterung, das gawk-Handbuch sagt es eindeutig. Andere awk-Varianten können es ebenfalls implementieren, aber es ist immer noch nicht POSIX. Versuchen Sie gawk --posix '{gsub (...)}' und es wird sich beschweren
MestreLion

2
@MestreLion, du meinst, es wird sich beschweren gawk --posix '{gensub(...)}'.
zweifelhaft

1
Obwohl Sie sich geirrt haben, dass POSIX awk die gensubFunktion hat, wurde Ihr Beispiel auf ein sehr begrenztes Szenario angewendet: Das gesamte Muster ist gruppiert, es kann nicht mit allen übereinstimmen, key=(value)wenn ich nur die valueTeile extrahieren möchte .
Miau

2

Ich hatte ein wenig Probleme damit, eine Bash-Funktion zu entwickeln, die die Antwort von Peter Tillemans umschließt, aber hier ist, was ich mir ausgedacht habe:

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

Ich fand, dass dies für das folgende Argument für reguläre Ausdrücke besser funktioniert als die awk-basierte Bash-Funktion von opsb, da ich nicht möchte, dass die "ms" gedruckt wird.

'([0-9]*)ms$'

Ich bevorzuge diese Lösung, da Sie die Teile der Gruppe sehen können, die die Erfassung begrenzen, während Sie sie auch weglassen. Könnte jemand erklären, wie das funktioniert? Ich kann diese Perl-Syntax in BASH nicht richtig zum Laufen bringen, weil ich sie nicht sehr gut verstehe - insbesondere die doppelten / einfachen Anführungszeichen$1
Demis

Es ist nicht etwas, was ich vorher oder seither getan habe, aber im Rückblick verkettet es zwei Zeichenfolgen, wobei die erste Zeichenfolge in doppelten Anführungszeichen steht (diese erste Zeichenfolge enthält eingebettete doppelte Anführungszeichen, die mit Backslash versehen sind) und die zweite Zeichenfolge in einfachen Anführungszeichen steht . Dann wird das Ergebnis dieser Verkettung als Argument an perl -e geliefert. Außerdem müssen Sie wissen, dass das erste $ 1 (das in doppelten Anführungszeichen) durch das erste Argument für die Funktion ersetzt wird, während das zweite $ 1 (das in einfachen Anführungszeichen) unberührt bleibt. Siehe dieses Beispiel
wytten

Ich verstehe, das macht jetzt etwas mehr Sinn. Wo also im Perl-Befehl befindet sich die Regex-Match- / Gruppenerfassungsdefinition? Ich sehe, Sie haben geschrieben '([0-9]*)ms$'- wird das als Argument angegeben (und die Zeichenfolge als weiteres Argument)? Und die Ausgabe von perl -ewird dann in den printfBefehl von bash eingefügt , um zu ersetzen %s, ist das richtig? Danke, ich hoffe, dass ich das nutzen kann.
Demis

1
Sie übergeben einen regulären Ausdruck in einfachen Anführungszeichen als einziges Argument an die Regex-Bash-Funktion. Beispiel
wytten
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.