AWK: accéder au groupe capturé à partir du modèle de ligne


229

Si j'ai une commande awk

pattern { ... }

et le modèle utilise un groupe de capture, comment puis-je accéder à la chaîne ainsi capturée dans le bloc?



Parfois (dans des cas simples), il est possible d'ajuster le séparateur de champ ( FS) et de choisir ce que l'on souhaite faire correspondre avec a $field. Le pré-formatage de l'entrée peut également aider.
Krzysztof Jabłoński

1
Il y a une meilleure réponse à la question en double.
Samuel Edwin Ward

2
Samuel Edwin Ward: C'est aussi une bonne réponse! Mais il faut aussi gawk(puisqu'il utilise gensub).
rampion

Réponses:


176

C'était une promenade dans le passé ...

J'ai remplacé awk par perl il y a longtemps.

Apparemment, le moteur d'expression régulière AWK ne capture pas ses groupes.

vous pourriez envisager d'utiliser quelque chose comme:

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

le drapeau -n fait que perl boucle sur chaque ligne comme le fait awk.


3
Apparemment, quelqu'un n'est pas d'accord. Cette page Web date de 2005: tek-tips.com/faqs.cfm?fid=5674 Elle confirme que vous ne pouvez pas réutiliser les groupes correspondants dans awk.
Peter Tillemans

3
Je préfère 'perl -n -p -e ...' à awk pour presque tous les cas d'utilisation, car il est plus flexible, plus puissant et a une syntaxe plus saine à mon avis.
Peter Tillemans

15
gawk! = awk. Ce sont des outils différents et gawkne sont pas disponibles par défaut dans la plupart des endroits.
Oli

6
L'OP a spécifiquement demandé une solution awk, donc je ne pense pas que ce soit une réponse.
Joppe

6
@Joppe, vous ne pouvez pas donner une solution awk s'il n'y a pas de solution. À la ligne 3, j'explique qu'AWK ne prend pas en charge la capture de groupes et j'ai donné une alternative, que le PO a apparemment appréciée parce que cette réponse a été acceptée. Comment pourrais-je mieux répondre à cette question?
Peter Tillemans

335

Avec gawk, vous pouvez utiliser la matchfonction pour capturer des groupes entre parenthèses.

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

exemple:

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

sorties cd.

Notez l'utilisation spécifique de gawk qui implémente la fonctionnalité en question.

Pour une alternative portable, vous pouvez obtenir des résultats similaires avec match()et substr.

exemple:

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

sorties cd.


4
Oui, les variantes gxxx ont beaucoup de qualités et de puissance GNU supplémentaires.
Peter Tillemans

Fonctionne également dans BusyBox awk.
MrMas

32

C'est quelque chose dont j'ai besoin tout le temps, j'ai donc créé une fonction bash pour cela. C'est basé sur la réponse de Glenn Jackman.

Définition

Ajoutez ceci à votre .bash_profile etc.

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

Usage

Capturer l'expression régulière pour chaque ligne du fichier

$ cat filename | regex '.*'

Capturez le premier groupe de capture regex pour chaque ligne du fichier

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

2
En quoi est-ce différent de l'utilisation grep -o?
bfontaine

@bfontaine Pourrait grep -oproduire des groupes capturés?
Olle Härstedt

1
@ OlleHärstedt Non, cela n'a pas pu. Il ne couvre votre cas d'utilisation que si vous n'avez pas de groupes de capture. Dans ce cas, ça devient moche avec les enchaînés grep -o.
bfontaine

15

Vous pouvez utiliser 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/

12
+1. Aussi, avec n'importe quel awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton


1
Ed Morton: cela mérite une réponse de haut niveau, je dirais. edit: uhm ... qui imprime RewriteRule (.*) http://www.mysite.net/$pour moi, ce qui est plus que le sous-groupe.
rampion


4

Vous pouvez également simuler la capture dans awk vanilla, sans extensions. Ce n'est pas intuitif cependant:

étape 1. utilisez gensub pour entourer les correspondances avec un caractère qui n'apparaît pas dans votre chaîne. étape 2. Utilisez la division contre le personnage. étape 3. Chaque autre élément du tableau divisé est votre groupe de capture.

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

3
Je suis presque certain que gensubc'est une gawkfonction spécifique. Qu'obtenez-vous de votre awk si vous tapez awk --version; -?). Bonne chance à tous.
shellter

6
Je suis absolument certain que gensub est un gawk-ism, bien que BusyBox awk l'ait également. Cette réponse pourrait également être implémentée en utilisant gsub, cependant:echo 'ab cb ad' | awk '{gsub(/a./,SUBSEP"&"SUBSEP);split($0,cap,SUBSEP);print cap[2]"|"cap[4]}'
dubiousjim

3
gensub () est une extension gawk, le manuel de gawk le dit clairement. D'autres variantes awk peuvent également l'implémenter, mais ce n'est toujours pas POSIX. Essayez gawk --posix '{gsub (...)}' et il se plaindra
MestreLion

2
@MestreLion, vous voulez dire qu'il se plaindra gawk --posix '{gensub(...)}'.
dubiousjim

1
Même si vous aviez tort à propos de POSIX awk ayant la gensubfonction, votre exemple s'applique à un scénario très limité: le motif entier est groupé, il ne peut pas correspondre à quelque chose comme tout key=(value)quand je veux extraire uniquement les valueparties.
Meow

2

J'ai eu un peu de mal à trouver une fonction bash qui enveloppe la réponse de Peter Tillemans, mais voici ce que j'ai trouvé:

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

J'ai trouvé que cela fonctionnait mieux que la fonction bash basée sur awk d'opsb pour l'argument d'expression régulière suivant, car je ne veux pas que le "ms" soit imprimé.

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

Je préfère cette solution, car vous pouvez voir les parties du groupe qui délimitent la capture, tout en les omettant. Cependant, quelqu'un pourrait-il expliquer comment cela fonctionne? Je ne peux pas faire fonctionner correctement cette syntaxe perl dans BASH, car je ne la comprends pas très bien - en particulier les guillemets doubles / simples autour$1
Demis

Ce n'est pas quelque chose que j'ai fait avant ou depuis, mais regarder en arrière ce qu'il fait revient à concaténer deux chaînes, la première chaîne étant entre guillemets doubles (cette première chaîne contient des guillemets doubles intégrés échappés avec barre oblique inverse) et la deuxième chaîne étant entre guillemets simples . Ensuite, le résultat de cette concaténation est fourni comme argument à perl -e. Vous devez également savoir que le premier $ 1 (celui entre guillemets doubles) est remplacé par le premier argument de la fonction, tandis que le deuxième $ 1 (celui entre guillemets simples) reste intact. Voir cet exemple
wytten

Je vois, c'est un peu plus logique maintenant. Alors, où dans la commande perl se trouve la définition de capture de correspondance / groupe regex? Je vois que vous avez écrit '([0-9]*)ms$'- est-ce fourni comme argument (et la chaîne un autre argument)? Et la sortie de perl -eest insérée dans la printfcommande de bash alors, pour remplacer %s, est-ce vrai? Merci, j'espère l'utiliser.
Demis

1
Vous passez une expression régulière entre guillemets simples comme seul argument de la fonction basge regex. Exemple
wytten
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.