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?

rampion
la source
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.

Peter Tillemans
la source
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.

glenn jackman
la source
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
opsb
la source
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/
Isvara
la source
12
+1. Aussi, avec n'importe quel awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton
5
C'est ce que dit la réponse de Glenn Jackman , à peu près.
rampion
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
ydrol
la source
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$'
wytten
la source
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