sustitución de comandos dentro de awk

3

¿Hay alguna manera de realizar la sustitución de comandos dentro de AWK y poder hacer referencia a los campos dentro del comando sustituido utilizando la $nnotación de AWK?

P.ej

find | awk '/txt$/ {nl = $(wc -l $NF); print nl}'

Esperaba que lo anterior imprimiera el número de líneas en cada .txtarchivo. En cambio, devuelve efectivamente el mismo resultado que:

find | awk '/txt$/ {print}'

P1: ¿hay alguna manera de realizar la sustitución de comandos dentro de awk?

P2: ¿por qué el primer encantamiento anterior falla silenciosamente y simplemente imprime los nombres de archivo?

Tenga en cuenta que lo anterior se ofrece solo a modo de ejemplo. No estoy preguntando cómo imprimir el número de líneas de cada archivo por algún otro medio. Ej. Porfor f in $(find -iname \*.txt); do wc -l $f; done

La pregunta es específicamente sobre cómo aprovechar la sustitución de comandos en los programas AWK.

Marcus Junius Brutus
fuente
También es relevante, debido a su forejemplo de bucle: ¿Por qué es un bucle sobre la salida de find una mala práctica?
Comodín
2
No publico esto como respuesta porque no aborda la sustitución de comandos en Awk, pero me pregunto seriamente si esa es NUNCA una solución adecuada. Su forbucle de ejemplo se puede resolver con solofind . -type f -name '*txt' -exec wc -l {} +
Wildcard
1
Para este caso, podría hacerlo wc -l **/*txtcon globstar activado en bash y construcciones similares en otros shells, si los nombres de archivo combinados no exceden ARG_MAX.
dave_thompson_085

Respuestas:

4

Primero, un descargo de responsabilidad: no analice la salida de find. El siguiente código es solo ilustrativo, de cómo incorporar la sustitución de comandos en un script Awk de tal manera que los comandos puedan actuar sobre partes de la entrada de Awk.

Para realmente hacer un recuento de la línea ( wc -l) en cada archivo encontrado con find(que es el caso del ejemplo del uso), sólo tiene que utilizar:

 find . -type f -name '*txt' -exec wc -l {} +

Sin embargo, para responder sus preguntas como se le preguntó:

Q1

Para responder a su Q1:

P1: ¿hay alguna manera de realizar la sustitución de comandos dentro de awk?

Por supuesto que hay una manera, desde man awk:

comando | getline [var] Ejecutar comando canalizando la salida en $ 0 o var, como se indica arriba, y RT.

Entonces (¡Mira la cita!):

find . | awk '/txt$/{"wc -l <\"" $NF "\"|cut -f1" | getline(nl); print(nl)}'

Tenga en cuenta que la cadena creada y, por lo tanto, el comando ejecutado es

wc -l <file

Para evitar la impresión de nombre de archivo de wc.

Bueno, evité un archivo "cerrar" necesario para ese comando (seguro para un par de archivos, pero técnicamente incorrecto). Realmente necesitas hacer:

find . | awk '/txt$/{
                       comm="wc -l <\"" $NF "\" | cut -f1"
                       comm | getline nl;
                       close (comm);
                       print nl 
                    }'

Eso también funciona para versiones anteriores de awk.
Recuerde evitar la impresión de un punto .con find ., eso hace que el código falle ya que un punto es un directorio y wc no puede usar eso.

O bien, evite el uso de valores de puntos:

find . | awk '/txt$/ && $NF!="." {  comm="wc -l <\"" $NF "\" | cut -f1"
                                    comm | getline nl;
                                    close (comm);
                                    print nl 
                                 }'

Puedes convertir eso en una línea, pero se verá bastante feo, piensa Me.

Q2

En cuanto a tu segunda pregunta:

P2: ¿por qué el primer encantamiento anterior falla silenciosamente y simplemente imprime los nombres de archivo?

Porque awk no analiza correctamente los comandos de shell. Entiende el comando como:

nl = $(wc -l $NF)
nl --> variable
$ --> pointer to a field
wc --> variable (that has zero value here)
-  --> minus sign
l  --> variable (that has a null string)
$  --> Pointer to a field
NF --> Last field

Luego, se l $NFconvierte en la concatenación de nulo y el texto dentro del campo las (un nombre de un archivo). La expansión de dicho texto como variable numérica es el valor numérico 0

Para awk, se convierte en:

nl = $( wc -l $NF)
nl = $ ( 0 - 0 )

Que se convierte en justo $0, la entrada de línea completa, que es (por el simple hallazgo de arriba) solo el nombre del archivo.

Entonces, todo lo anterior solo imprimirá el nombre del archivo (bueno, técnicamente, toda la línea).

Flecha
fuente
1

Use en "weak quotes"lugar de la 'strong quotes'expansión de subshell dentro de un awkscript, pero hacerlo en su ejemplo no sería una implementación particularmente valiosa. También se ve fantásticamente feo:

$ awk "END { print \"$(echo hello)\"} " < /dev/null
hello
DopeGhoti
fuente
Su ejemplo funciona pero no se puede acceder a los campos dentro de la sustitución de comandos utilizando la $nnotación. Por ejemplo, ls | awk "{ print \"$(echo hello $NF)\"} "no imprime el nombre de cada archivo después del "hola"
Marcus Junius Brutus
Necesitas escapar del $modo que bashno lo consuma antes de awkpoder verlo. E incluso entonces, la subshell no sabe que está dentro awk, por lo que cualquier awkanálisis sería después de cualquier procesamiento de shell. Por print \$$(echo 1)lo tanto, debe reducirse a print $1.
DopeGhoti
1
Esto no tiene esperanza de funcionar. Incluso si las citas se realizan correctamente, el shell ejecuta el comando de shell solo una vez, justo antes de iniciar awk. No hay forma (citando) de devolver un valor awk al shell para su ejecución. Y el comando que el OP está pidiendo para que funcione tiene una lista de valores proporcionados por find, no solo uno.
Flecha