AWK: acceder al grupo capturado desde el patrón de línea

229

Si tengo un comando awk

pattern { ... }

y el patrón usa un grupo de captura, ¿cómo puedo acceder a la cadena capturada en el bloque?

rampion
fuente
A veces (en casos simples) es posible ajustar el separador de campo ( FS) y elegir lo que a uno le gustaría combinar con a $field. Preformatear la entrada también podría ayudar.
Krzysztof Jabłoński
1
Hay una mejor respuesta en la pregunta duplicada.
Samuel Edwin Ward
2
Samuel Edwin Ward: ¡Esa también es una buena respuesta! Pero también requiere gawk(ya que usa gensub).
rampion

Respuestas:

176

Ese fue un paseo por el carril de la memoria ...

Reemplacé awk por perl hace mucho tiempo.

Aparentemente, el motor de expresión regular AWK no captura sus grupos.

podrías considerar usar algo como:

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

la bandera -n hace que perl recorra cada línea como lo hace awk.

Peter Tillemans
fuente
3
Al parecer, alguien no está de acuerdo. Esta página web es de 2005: tek-tips.com/faqs.cfm?fid=5674 Confirma que no puede reutilizar grupos coincidentes en awk.
Peter Tillemans
3
Prefiero 'perl -n -p -e ...' sobre awk para casi todos los casos de uso, ya que es más flexible, más potente y tiene una sintaxis más sensata en mi opinión.
Peter Tillemans
15
gawk! = awk. Son herramientas diferentes y gawkno están disponibles por defecto en la mayoría de los lugares.
Oli
66
El OP solicitó específicamente una solución awk, por lo que no creo que sea una respuesta.
Joppe
66
@Joppe, no puedes dar una solución awk si no hay solución. En la línea 3 explico que AWK no admite la captura de grupos y di una alternativa, que el OP aparentemente apreció porque esta respuesta fue aceptada. ¿Cómo podría responder mejor esta pregunta?
Peter Tillemans
335

Con gawk, puede usar la matchfunción para capturar grupos entre paréntesis.

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

ejemplo:

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

salidas cd.

Tenga en cuenta el uso específico de gawk que implementa la función en cuestión.

Para una alternativa portátil, puede lograr resultados similares con match()y substr.

ejemplo:

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

salidas cd.

Glenn Jackman
fuente
44
Sí, las variantes de gxxx tienen muchas bondades y potencias adicionales de GNU.
Peter Tillemans
Funciona en BusyBox awk también.
MrMas
32

Esto es algo que necesito todo el tiempo, así que creé una función bash para ello. Se basa en la respuesta de Glenn Jackman.

Definición

Agregue esto a su .bash_profile, etc.

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

Uso

Capture expresiones regulares para cada línea en el archivo

$ cat filename | regex '.*'

Capture el primer grupo de captura de expresiones regulares para cada línea en el archivo

$ cat filename | regex '(.*)' 1
opsb
fuente
2
¿Cómo es diferente de usar grep -o?
bfontaine
@bfontaine ¿Podría grep -ogenerar grupos capturados?
Olle Härstedt
1
@ OlleHärstedt No, no podría. Solo cubre su caso de uso cuando no tiene grupos de captura. En ese caso se pone feo con los encadenados grep -o.
bfontaine
15

Puedes usar 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
fuente
12
+1. Además, con cualquier awk:awk 'match($0, /.*(http.*?)\$/) { print substr($0,RSTART,RLENGTH) }'
Ed Morton
55
Eso es lo que dice la respuesta de Glenn Jackman , más o menos.
rampion
1
Ed Morton: eso merece una respuesta de alto nivel, diría. edit: uhm ... eso imprime RewriteRule (.*) http://www.mysite.net/$para mí, que es más que el subgrupo.
rampion
4

También puede simular la captura en vainilla awk, sin extensiones. Sin embargo, no es intuitivo:

paso 1. usa gensub para rodear coincidencias con algún carácter que no aparece en tu cadena. paso 2. Utiliza split contra el personaje. paso 3. Cualquier otro elemento en la matriz dividida es tu grupo de captura.

$ echo 'ab cb ad' | awk '{split (gensub (/ a ./, SUBSEP "y" SUBSEP, "g", $ 0), cap, SUBSEP); gorra de impresión [2] "|" gorra [4]; } '
ab | ad
ydrol
fuente
3
Estoy casi seguro de que gensubes una gawkfunción específica. ¿Qué obtienes de tu awk si escribes awk --version; -?). Buena suerte a todos.
shellter
66
Estoy completamente seguro de que gensub es un gawk-ism, aunque BusyBox awk también lo tiene. Sin embargo, esta respuesta también podría implementarse usando gsub:echo 'ab cb ad' | awk '{gsub(/a./,SUBSEP"&"SUBSEP);split($0,cap,SUBSEP);print cap[2]"|"cap[4]}'
dubiousjim
3
gensub () es una extensión de gawk, el manual de gawk lo dice claramente. Otras variantes de awk también pueden implementarlo, pero todavía no es POSIX. Prueba gawk --posix '{gsub (...)}' y se quejará
MestreLion
2
@MestreLion, quieres decir que se quejará gawk --posix '{gensub(...)}'.
dubiousjim
1
A pesar de que estaba equivocado acerca de que POSIX awk tenía la gensubfunción, su ejemplo se aplicó a un escenario muy limitado: todo el patrón está agrupado, no puede coincidir con algo como todo key=(value)cuando quiero extraer solo las valuepartes.
Miau
2

Me costó un poco encontrar una función bash que envuelva la respuesta de Peter Tillemans, pero esto es lo que se me ocurrió:

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

Encontré que esto funcionó mejor que la función bash basada en awk de opsb para el siguiente argumento de expresión regular, porque no quiero que se imprima el "ms".

'([0-9]*)ms$'
wytten
fuente
Prefiero esta solución, ya que puede ver las partes del grupo que delimitan la captura, al tiempo que las omite. Sin embargo, ¿podría alguien explicar cómo funciona esto? No puedo hacer que esta sintaxis de Perl funcione correctamente en BASH, porque no lo entiendo muy bien, especialmente las comillas dobles / simples alrededor$1
Demis
No es algo que haya hecho antes o desde entonces, pero mirar hacia atrás lo que está haciendo es concatenar dos cadenas, la primera cadena entre comillas dobles (esta primera cadena contiene comillas dobles incrustadas con barra invertida) y la segunda cadena entre comillas simples. . Luego, el resultado de esa concatenación se proporciona como argumento para perl -e. También debe saber que el primer $ 1 (el que está entre comillas dobles) se sustituye con el primer argumento de la función, mientras que el segundo $ 1 (el que está entre comillas simples) no se toca. Ver este ejemplo
wytten el
Ya veo, eso tiene un poco más de sentido ahora. Entonces, ¿en qué parte del comando perl está la definición de captura de expresiones / grupos de expresiones regulares? Veo que escribiste '([0-9]*)ms$': ¿se proporciona como argumento (y la cadena es otro argumento)? Y la salida de perl -ese está insertando en el printfcomando de bash entonces, para reemplazar %s, ¿es correcto? Gracias, espero usar esto.
Demis
1
Pasa una expresión regular encerrada entre comillas simples como único argumento para la función regex bash. Ejemplo
wytten el