El comando AWK se remonta a los primeros días de Unix. Es parte del estándar POSIX y debería estar disponible en cualquier sistema tipo Unix. Y más allá.
Aunque a veces se le desacredita por su antigüedad o falta de funciones en comparación con un lenguaje multipropósito como Perl, AWK sigue siendo una herramienta que me gusta usar en mi trabajo diario. A veces para escribir programas relativamente complejos, pero también debido a las poderosas frases ingeniosas que puede escribir para resolver problemas con sus archivos de datos.
Entonces, este es exactamente el propósito de este artículo. Mostrándole cómo puede aprovechar el poder de AWK en menos de 80 caracteres para realizar tareas útiles. Este artículo no pretende ser un tutorial completo de AWK, pero aún así he incluido algunos comandos básicos al principio, por lo que incluso si tienes poca o ninguna experiencia previa, puedes captar los conceptos básicos de AWK.
Mis archivos de muestra para este tutorial de AWK
Todas las frases ingeniosas descritas en ese artículo se probarán en el mismo archivo de datos:
cat file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Puede obtener una copia de ese archivo en línea en GitHub.
Conozca las variables predefinidas y automáticas en AWK
AWK admite un par de variables automáticas y predefinidas para ayudarle a escribir sus programas. Entre ellos encontrarás a menudo:
RS–El separador de registros. AWK procesa sus datos un registro a la vez. El separador de registros es el delimitador utilizado para dividir el flujo de datos de entrada en registros. De forma predeterminada, este es el carácter de nueva línea. Entonces, si no lo cambia, un registro es una línea del archivo de entrada.
NR– El número de registro de entrada actual. Si está utilizando el delimitador de nueva línea estándar para sus registros, esto coincidirá con el número de línea de entrada actual.
FS/OFS–Los caracteres utilizados como separador de campo. Una vez que AWK lee un registro, lo divide en diferentes campos según el valor deFS
. Cuando AWK imprima un registro en la salida, volverá a unir los campos, pero esta vez, usando elOFS
separador en lugar delFS
separador. Generalmente,FS
yOFS
son iguales, pero esto no es obligatorio. “espacio en blanco” es el valor predeterminado para ambos.
NF– El número de campos del registro actual. Si está utilizando el delimitador estándar de "espacio en blanco" para sus campos, esto coincidirá con la cantidad de palabras en el registro actual.
Hay otras variables AWK más o menos estándar disponibles, por lo que vale la pena consultar su manual de implementación de AWK particular para obtener más detalles. Sin embargo, este subconjunto ya es suficiente para empezar a escribir chistes interesantes.
A. Uso básico del comando AWK
1. Imprime todas las líneas
Este ejemplo es prácticamente inútil, pero será una buena introducción a la sintaxis de AWK:
awk '1 { print }' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Los programas AWK están hechos de uno o variospattern { action }
declaraciones.
Si, para un registro determinado (“línea”) del archivo de entrada, el patrón se evalúa como un valor distinto de cero (equivalente a “verdadero” en AWK), se ejecutan los comandos en el bloque de acción correspondiente. En el ejemplo anterior, dado que1
es una constante distinta de cero, la{ print }
El bloque de acción se ejecuta para cada registro de entrada.
Otro truco es{ print }
es el bloque de acción predeterminado que utilizará AWK si no especifica uno explícitamente. Entonces el comando anterior se puede acortar como:
awk 1 file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Casi igual de inútil, el siguiente programa AWK consumirá su entrada pero no producirá nada en la salida:
awk 0 file
2. Eliminar el encabezado de un archivo
awk 'NR>1' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Recuerde, esto equivale a escribir explícitamente:
awk 'NR>1 { print }' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Esta frase escrita escribirá registros del archivo de entrada excepto el primero, ya que en ese caso la condición es1>1
lo cual obviamente no es cierto.
Dado que este programa utiliza los valores predeterminados paraRS
, en la práctica descartará la primera línea del archivo de entrada.
3. Imprimir líneas en un rango
Esto es sólo una generalización del ejemplo anterior, y no merece muchas explicaciones, excepto decir&&
es lo lógicoand
operador:
awk 'NR>1 && NR < 4' file
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
4. Eliminar líneas que solo contienen espacios en blanco
awk 'NF' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
AWK divide cada registro en campos, según el separador de campos especificado en elFS
variable. El separador de campo predeterminado es uno o varios caracteres de espacio en blanco (también conocidos como espacios o tabulaciones). Con esa configuración, cualquier registro que contenga al menos un carácter que no sea un espacio en blanco contendrá al menos un campo.
En otras palabras, el único caso en el queNF
es 0 (“falso”) es cuando el registro contiene solo espacios. Entonces, esa línea única solo imprimirá registros que contengan al menos un carácter que no sea un espacio.
5. Eliminar todas las líneas en blanco
awk '1' RS='' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Esta frase ingeniosa se basa en una oscura regla POSIX que especifica si elRS
se establece en la cadena vacía, "entonces los registros se separan por secuencias que consisten en una más una o más líneas en blanco”.
Vale la pena mencionar que en la terminología POSIX, una línea en blanco es una línea completamente vacía. Las líneas que contienen sólo espacios en blanco no cuentan como "en blanco".
6. Extrayendo campos
Este es probablemente uno de los casos de uso más comunes de AWK: extraer algunas columnas del archivo de datos.
awk '{ print $1, $3}' FS=, OFS=, file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
,
,
17,abhishek
Aquí, configuro explícitamente los separadores de campo de entrada y salida en coma. Cuando AWK divide un registro en campos, almacena el contenido del primer campo en $1, el contenido del segundo campo en $2 y así sucesivamente. No uso eso aquí, pero vale la pena mencionar que $0 es el registro completo.
En esta frase, habrás notado que uso un bloque de acción sin patrón. En ese caso, se supone 1 (“verdadero”) para el patrón, por lo que el bloque de acción se ejecuta para cada registro.
Dependiendo de sus necesidades, es posible que no produzca lo que nos gustaría para líneas en blanco o solo espacios en blanco. En ese caso, esa segunda versión podría ser un poco mejor:
awk 'NF { print $1, $3 }' FS=, OFS=, file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
17,abhishek
En ambos casos, pasé valores personalizados paraFS
yOFS
en la línea de comando. Otra opción sería utilizar un especialBEGIN
bloque dentro del programa AWK para inicializar esas variables antes de que se lea el primer registro. Entonces, dependiendo de tus gustos, es posible que prefieras escribir eso:
awk 'BEGIN { FS=OFS="," } NF { print $1, $3 }' file
CREDITS,USER
99,sylvain
52,sonia
52,sonia
25,sonia
10,sylvain
8,öle
,
17,abhishek
Vale la pena mencionar aquí que también puedes usarEND
bloques para realizar algunas tareas después de que se haya leído el último registro. Como lo veremos ahora mismo. Dicho esto, admito que esto está lejos de ser perfecto ya que las líneas de solo espacios en blanco no se manejan con elegancia. Pronto veremos una posible solución, pero antes hagamos algunos cálculos...
7. Realizar cálculos por columnas
AWK admite operadores aritméticos estándar. Y convertirá valores entre texto y números automáticamente según el contexto. Además, puede utilizar sus propias variables para almacenar valores intermedios. Todo eso le permite escribir programas compactos para realizar cálculos en columnas de datos:
awk '{ SUM=SUM+$1 } END { print SUM }' FS=, OFS=, file
263
O, de manera equivalente, usar el+=
sintaxis abreviada:
awk '{ SUM+=$1 } END { print SUM }' FS=, OFS=, file
263
Tenga en cuenta que no es necesario declarar las variables AWK antes de su uso. Se supone que una variable indefinida contiene la cadena vacía. Lo cual, según las reglas de conversión de tipos AWK, es igual al número 0. Debido a esa característica, no me molesté en manejar explícitamente el caso en el que$1
contiene texto (en el encabezado), espacios en blanco o simplemente nada. En todos esos casos, contará como 0 y no interferirá con nuestra suma. Por supuesto, sería diferente si en su lugar realizara multiplicaciones. Entonces, ¿por qué no utilizarías la sección de comentarios para sugerir una solución para ese caso?
8. Contando el número de líneas no vacías
Ya he mencionado elEND
gobernar antes. Aquí hay otra posible aplicación para contar el número de líneas no vacías en un archivo:
awk '/./ { COUNT+=1 } END { print COUNT }' file
9
Aquí usé elCOUNT
variable y la incrementó (+=1
) para cada línea que coincida con la expresión regular/./
. Es decir, cada línea contiene al menos un carácter. Finalmente, el bloque END se utiliza para mostrar el resultado final una vez que se ha procesado todo el archivo. No hay nada especial en el nombre.COUNT
. podría haber usadoCount
,count
,n
,xxxx
o cualquier otro nombre que cumpla con las reglas de nomenclatura de variables de AWK
Sin embargo, ¿es correcto este resultado? Bueno, depende de tu definición de línea "vacía". Si considera que solo las líneas en blanco (según POSIX) están vacías, entonces esto es correcto. ¿Pero tal vez preferirías considerar vacías también las líneas de solo espacios en blanco?
awk 'NF { COUNT+=1 } END { print COUNT }' file
8
Esta vez el resultado es diferente ya que la versión posterior también ignora las líneas de solo espacios en blanco, mientras que la versión inicial solo ignoraba las líneas en blanco. ¿Puedes ver la diferencia? Te dejo que lo averigües tú mismo. ¡No dudes en utilizar la sección de comentarios si esto no está lo suficientemente claro!
Finalmente, si solo está interesado en las líneas de datos y dado mi archivo de datos de entrada particular, podría escribirlo en su lugar:
awk '+$1 { COUNT+=1 } END { print COUNT }' file
7
Funciona gracias a las reglas de conversión de tipos AWK. El signo unario más en el patrón obliga a evaluar 1 dólar en un contexto numérico. En mi expediente, los registros de datos contienen un número en su primer campo. Los registros que no son datos (encabezados, líneas en blanco, líneas solo con espacios en blanco) contienen texto o nada. Todos ellos son iguales a 0 cuando se convierten a números.
Tenga en cuenta que con la última solución, también se descartará un registro de un usuario que eventualmente tenga 0 créditos.
B. Usando matrices en AWK
Las matrices son una característica poderosa de AWK. Todos los arreglos en AWK son arreglos asociativos, por lo que permiten asociar una cadena arbitraria con otro valor. Si está familiarizado con otros lenguajes de programación, es posible que los conozca como hashes, tablas asociativas, diccionarios o mapas.
9. Un ejemplo sencillo de matriz AWK
Imaginemos que quiero saber el crédito total de todos los usuarios. Puedo almacenar una entrada para cada usuario en una matriz asociativa y cada vez que encuentro un registro para ese usuario, incremento el valor correspondiente almacenado en la matriz.
awk '+$1 { CREDITS[$3]+=$1 }
END { for (NAME in CREDITS) print NAME, CREDITS[NAME] }' FS=, file
abhishek 17
sonia 129
öle 8
sylvain 109
Admito que esto ya no es una frase sencilla. Principalmente debido a lafor
bucle utilizado para mostrar el contenido de la matriz después de que se haya procesado el archivo. Entonces, volvamos ahora a ejemplos más breves:
10. Identificar líneas duplicadas usando AWK
Los arrays, al igual que otras variables de AWK, se pueden utilizar tanto en bloques de acción como en patrones. Aprovechando eso, podemos escribir una sola línea para imprimir solo líneas duplicadas:
awk 'a[$0]++' file
52,01 dec 2018,sonia,team
El++
operador es el operador post-incremento heredado de la familia de lenguajes C (de cuyo AWK es un miembro orgulloso, gracias a que Brian Kernighan ha sido uno de sus autores originales).
Como su nombre lo indica, el operador post-incremento incrementa (“agrega 1”) una variable, pero solo después de que se haya tomado su valor para la evaluación de la expresión envolvente.
En ese caso,a[$0]
Se evalúa para ver si el registro se imprimirá o no, y una vez tomada la decisión, en todos los casos, se incrementa la entrada del array.
Entonces, la primera vez que se lee un registro,a[$0]
no está definido y, por lo tanto, es equivalente a cero para AWK. Entonces ese primer registro no se escribe en la salida. Luego esa entrada se cambia de cero a uno.
La segunda vez que se lee el mismo registro de entrada,a[$0]
ahora es 1. Eso es “verdadero”. La línea será impresa. Sin embargo, antes de eso, la entrada de la matriz se actualiza de 1 a 2. Y así sucesivamente.
11. Eliminar líneas duplicadas
Como corolario de la frase anterior, es posible que deseemos eliminar líneas duplicadas:
awk '!a[$0]++' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
La única diferencia es el uso del operador lógico, no (!
) que invierten el valor de verdad de la expresión. Lo que era falso se vuelve verdad y lo que era verdadero se vuelve falso. Lo lógico no tiene absolutamente ninguna influencia sobre el++
incremento posterior que funciona exactamente como antes.
C. Magia separadora de campos y registros
12. Cambiar los separadores de campo
awk '$1=$1' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
Ese programa establece elFS
yOFS
variable para utilizar una coma como separador de campo de entrada y un punto y coma como separador de campo de salida. Dado que AWK no cambia el registro de salida siempre que no haya cambiado un campo, el$1=$1
El truco se utiliza para obligar a AWK a romper el registro y volver a ensamblarlo usando el separador de campo de salida.
Recuerde aquí el bloque de acción predeterminado es{ print }
. Entonces podrías reescribirlo de manera más explícita como:
awk '$1=$1 { print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
Es posible que hayas notado que ambos ejemplos también eliminan líneas vacías. ¿Por qué? Bueno, recuerda las reglas de conversión de AWK: una cadena vacía es "falso". Todas las demás cadenas son "verdaderas". La expresion$1=$1
es una afectación que altera$1
. Sin embargo, esta también es una expresión. Y se evalúa al valor de$1
–que es “falso” para la cadena vacía. Si realmente quieres todas las líneas, es posible que tengas que escribir algo como esto:
awk '($1=$1) || 1 { print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
¿Recuerdas el&&
¿operador? Era el Y lógico.||
es el OR lógico. El paréntesis es necesario aquí debido a las reglas de precedencia de los operadores. Sin ellos, el patrón se habría interpretado erróneamente como$1=($1 || 1)
en cambio. Te dejo como ejercicio que pruebes cómo el resultado hubiera sido diferente en aquel entonces.
Finalmente, si no eres muy aficionado a la aritmética, apuesto a que preferirás esa solución más simple:
awk '{ $1=$1; print }' FS=, OFS=';' file
CREDITS;EXPDATE;USER;GROUPS
99;01 jun 2018;sylvain;team:::admin
52;01 dec 2018;sonia;team
52;01 dec 2018;sonia;team
25;01 jan 2019;sonia;team
10;01 jan 2019;sylvain;team:::admin
8;12 jun 2018;öle;team:support
17;05 apr 2019;abhishek;guest
13. Eliminar múltiples espacios
awk '$1=$1' file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
Este es casi el mismo programa que el anterior. Sin embargo, dejé los separadores de campos con sus valores predeterminados. Por lo tanto, se utilizan varios espacios en blanco como separador de campo de entrada, pero solo se utiliza un espacio como separador de campo de salida. Esto tiene el agradable efecto secundario de fusionar múltiples espacios en blanco en un solo espacio.
14. Unir líneas usando AWK
ya hemos usadoOFS
, el separador de campo de salida. Como habrás adivinado, tiene laORS
contraparte para especificar el separador de registros de salida:
awk '{ print $3 }' FS=, ORS=' ' file; echo
USER sylvain sonia sonia sonia sylvain öle abhishek
Aquí utilicé un espacio después de cada registro en lugar de un carácter de nueva línea. Esta frase breve es suficiente en algunos casos de uso, pero todavía tiene algunos inconvenientes.
Lo más obvio es que no descarta las líneas que solo contienen espacios en blanco (los espacios adicionales después de öle provienen de eso). Entonces, puedo terminar usando una expresión regular simple:
awk '/[^[:space:]]/ { print $3 }' FS=, ORS=' ' file; echo
USER sylvain sonia sonia sonia sylvain öle abhishek
Es mejor ahora, pero todavía existe un posible problema. Será más obvio si cambiamos el separador a algo visible:
awk '/[^[:space:]]/ { print $3 }' FS=, ORS='+' file; echo
USER+sylvain+sonia+sonia+sonia+sylvain+öle+abhishek+
Hay un separador adicional al final de la línea, porque el separador de campo se escribe después de cada registro. Incluyendo el último.
Para solucionarlo, reescribiré el programa para mostrar un separador personalizado antes del registro, comenzando desde el segundo registro de salida.
awk '/[^[:space:]]/ { print SEP $3; SEP="+" }' FS=, ORS='' file; echo
USER+sylvain+sonia+sonia+sonia+sylvain+öle+abhishek
Como yo mismo me encargo de agregar el separador, también configuro el separador de registros de salida AWK estándar en la cadena vacía. Sin embargo, cuando empiece a lidiar con separadores o formato, puede ser la señal de que debería considerar usar la función printf en lugar de laprint
declaración. Como lo veremos ahora mismo.
D. Formato de campo
Ya he mencionado la relación entre los lenguajes de programación AWK y C. Entre otras cosas, AWK hereda de la biblioteca estándar del lenguaje C el potenteprintf
función, lo que permite un gran control sobre el formato del texto enviado a la salida.
Elprintf
La función toma un formato como primer argumento, que contiene texto sin formato que se generará palabra por palabra y comodines utilizados para formatear diferentes secciones de la salida. Los comodines se identifican con el%
personaje. El ser más común%s
(para formatear cadenas),%d
(para formato de números enteros) y%f
(para formato de números de punto flotante). Como esto puede resultar bastante abstracto, veamos un ejemplo:
awk '+$1 { printf("%s ", $3) }' FS=, file; echo
sylvain sonia sonia sonia sylvain öle abhishek
Puedes notar, como lo contrario de loprint
declaración, laprintf
La función no utiliza elOFS
yORS
valores. Entonces, si desea algún separador, debe mencionarlo explícitamente como lo hice yo agregando un carácter de espacio al final de la cadena de formato. Éste es el precio a pagar por tener el control total de la producción.
Si bien no es en absoluto un especificador de formato, esta es una excelente ocasión para presentar eln
notación que se puede utilizar en cualquier cadena AWK para representar un carácter de nueva línea.
awk '+$1 { printf("%sn", $3) }' FS=, file
sylvain
sonia
sonia
sonia
sylvain
öle
abhishek
15. Producir resultados tabulares
AWK impone un formato de datos de registro/campo basado en delimitadores. Sin embargo, utilizando elprintf
función, también puede producir una salida tabular de ancho fijo. Debido a que cada especificador de formato en unprintf
La declaración puede aceptar un parámetro de ancho opcional:
awk '+$1 { printf("%10s | %4dn", $3, $1) }' FS=, file
sylvain | 99
sonia | 52
sonia | 52
sonia | 25
sylvain | 10
öle | 8
abhishek | 17
Como puede ver, al especificar el ancho de cada campo, AWK los rellena hacia la izquierda con espacios. Para el texto, normalmente es preferible rellenar a la derecha, algo que se puede lograr utilizando un número de ancho negativo. Además, para los números enteros, es posible que deseemos rellenar los campos con ceros en lugar de espacios. Esto se puede obtener usando un 0 explícito antes del ancho del campo:
awk '+$1 { printf("%-10s | %04dn", $3, $1) }' FS=, file
sylvain | 0099
sonia | 0052
sonia | 0052
sonia | 0025
sylvain | 0010
öle | 0008
abhishek | 0017
16. Manejo de números de coma flotante
El%f
El formato no merece muchas explicaciones…
awk '+$1 { SUM+=$1; NUM+=1 } END { printf("AVG=%f",SUM/NUM); }' FS=, file
AVG=37.571429
… excepto tal vez para decir que casi siempre deseas establecer explícitamente el ancho del campo y la precisión del resultado mostrado:
awk '+$1 { SUM+=$1; NUM+=1 } END { printf("AVG=%6.1f",SUM/NUM); }' FS=, file
AVG= 37.6
Aquí, el ancho del campo es 6, lo que significa que el campo ocupará el espacio de 6 caracteres (incluido el punto y, finalmente, se rellenará con espacios a la izquierda, como suele ocurrir). La precisión de .1 significa que queremos mostrar el número con 1 decimal después del punto. Te dejo adivinar que%06.1
se mostraría en su lugar.
E. Usar funciones de cadena en AWK
Además deprintf
función, AWK contiene algunas otras funciones interesantes de manipulación de cadenas. En ese ámbito, las implementaciones modernas como Gawk tienen un conjunto más rico de funciones internas al precio de una menor portabilidad. Por mi parte, me quedaré aquí con solo algunas funciones definidas por POSIX que deberían funcionar igual en cualquier lugar.
17. Convertir texto a mayúsculas
Este lo uso mucho porque maneja muy bien los problemas de internacionalización:
awk '$3 { print toupper($0); }' file
99,01 JUN 2018,SYLVAIN,TEAM:::ADMIN
52,01 DEC 2018,SONIA,TEAM
52,01 DEC 2018,SONIA,TEAM
25,01 JAN 2019,SONIA,TEAM
10,01 JAN 2019,SYLVAIN,TEAM:::ADMIN
8,12 JUN 2018,ÖLE,TEAM:SUPPORT
17,05 APR 2019,ABHISHEK,GUEST
De hecho, esta es probablemente la mejor y más portátil solución para convertir texto a mayúsculas desde el shell.
18. Cambiando parte de una cuerda
Utilizando elsubstr
comando, puede dividir una cadena de caracteres en una longitud determinada. Aquí lo uso para poner en mayúscula sólo el primer carácter del tercer campo:
awk '{ $3 = toupper(substr($3,1,1)) substr($3,2) } $3' FS=, OFS=, file
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,Sylvain,team:::admin
52,01 dec 2018,Sonia,team
52,01 dec 2018,Sonia,team
25,01 jan 2019,Sonia,team
10,01 jan 2019,Sylvain,team:::admin
8,12 jun 2018,Öle,team:support
17,05 apr 2019,Abhishek,guest
Elsubstr
La función toma la cadena inicial, el índice (basado en 1) del primer carácter a extraer y el número de caracteres a extraer. Si falta ese último argumento,substr
toma todos los caracteres restantes de la cadena.
Entonces,substr($3,1,1)
evaluará al primer carácter de$3
, ysubstr($3,2)
a los restantes.
19. Dividir campos en subcampos
El modelo de datos de campo de registro de AWK es realmente bueno. Sin embargo, a veces desea dividir los campos en varias partes según algún separador interno:
awk '+$1 { split($2, DATE, " "); print $1,$3, DATE[2], DATE[3] }' FS=, OFS=, file
99,sylvain,jun,2018
52,sonia,dec,2018
52,sonia,dec,2018
25,sonia,jan,2019
10,sylvain,jan,2019
8,öle,jun,2018
17,abhishek,apr,2019
Sorprendentemente, esto funciona incluso si algunos de mis campos están separados por más de un espacio en blanco. Principalmente por razones históricas, cuando el separador es un espacio único,split
considerará "los elementos están separados por espacios en blanco". Y no sólo por uno solo. ElFS
La variable especial sigue la misma convención.
Sin embargo, en el caso general, una cadena de caracteres coincide con un carácter. Entonces, si necesitas algo más complejo, debes recordar que el separador de campo es una expresión regular extendida.
Como ejemplo, veamos cómo se manejaría el campo de grupo que parece ser un campo multivalor usando dos puntos como separador:
awk '+$1 { split($4, GRP, ":"); print $3, GRP[1], GRP[2] }' FS=, file
sylvain team
sonia team
sonia team
sonia team
sylvain team
öle team support
abhishek guest
Mientras que hubiera esperado mostrar hasta dos grupos por usuario, muestra sólo uno para la mayoría de ellos. Ese problema se debe a las múltiples apariciones del separador. Entonces, la solución es:
awk '+$1 { split($4, GRP, /:+/); print $3, GRP[1], GRP[2] }' FS=, file
sylvain team admin
sonia team
sonia team
sonia team
sylvain team admin
öle team support
abhishek guest
Las barras diagonales en lugar de las comillas indican que el literal es una expresión regular en lugar de una cadena simple, y el signo más indica que esta expresión coincidirá con una o varias apariciones del carácter anterior. Entonces, en ese caso, cada separador se compone (de la secuencia más larga de) uno o varios dos puntos consecutivos.
20. Buscar y reemplazar con comandos AWK
Hablando de expresiones regulares, a veces quieres realizar una sustitución como la seds///g
comando, pero sólo en un campo. Elgsub
El comando es lo que necesitas en ese caso:
awk '+$1 { gsub(/ +/, "-", $2); print }' FS=, file
99 01-jun-2018 sylvain team:::admin
52 01-dec-2018 sonia team
52 01-dec-2018 sonia team
25 01-jan-2019 sonia team
10 01-jan-2019 sylvain team:::admin
8 12-jun-2018 öle team:support
17 05-apr-2019 abhishek guest
Elgsub
La función toma una expresión regular para buscar, una cadena de reemplazo y la variable que contiene el texto que se va a modificar en su lugar. Si falta ese valor posterior, se supone $0.
F. Trabajar con comandos externos en AWK
Otra gran característica de AWK es que puedes invocar fácilmente comandos externos para procesar tus datos. Básicamente hay dos formas de hacerlo: utilizando elsystem
instrucción para invocar un programa y permitirle mezclar su salida en el flujo de salida AWK. O usar una tubería para que AWK pueda capturar la salida del programa externo para un control más preciso del resultado.
Estos pueden ser temas extensos por sí solos, pero aquí hay algunos ejemplos simples para mostrarle el poder detrás de esas características.
21. Agregar la fecha encima de un archivo
awk 'BEGIN { printf("UPDATED: "); system("date") } /^UPDATED:/ { next } 1' file
UPDATED: Thu Feb 15 00:31:03 CET 2018
CREDITS,EXPDATE,USER,GROUPS
99,01 jun 2018,sylvain,team:::admin
52,01 dec 2018,sonia,team
52,01 dec 2018,sonia,team
25,01 jan 2019,sonia,team
10,01 jan 2019,sylvain,team:::admin
8,12 jun 2018,öle,team:support
17,05 apr 2019,abhishek,guest
En ese programa AWK, comienzo mostrando el trabajo ACTUALIZADO. Luego, el programa invoca el comando de fecha externo, que enviará su resultado en la salida justo después del texto producido por AWK en esa etapa.
El resto del programa AWK simplemente elimina una declaración de actualización eventualmente presente en el archivo e imprime todas las demás líneas (con la regla1
).
Observe lanext
declaración. Se utiliza para cancelar el procesamiento del registro actual. Es una forma estándar de ignorar algunos registros del archivo de entrada.
22. Modificar un campo externamente
Para casos más complejos, es posible que deba considerar la| getline VARIABLE
modismo de AWK:
awk '+$1 { CMD | getline $5; close(CMD); print }' CMD="uuid -v4" FS=, OFS=, file
99,01 jun 2018,sylvain,team:::admin,5e5a1bb5-8a47-48ee-b373-16dc8975f725
52,01 dec 2018,sonia,team,2b87e9b9-3e75-4888-bdb8-26a9b34facf3
52,01 dec 2018,sonia,team,a5fc22b5-5388-49be-ac7b-78063cbbe652
25,01 jan 2019,sonia,team,3abb0432-65ef-4916-9702-a6095f3fafe4
10,01 jan 2019,sylvain,team:::admin,592e9e80-b86a-4833-9e58-1fe2428aa2a2
8,12 jun 2018,öle,team:support,3290bdef-fd84-4026-a02c-46338afd4243
17,05 apr 2019,abhishek,guest,e213d756-ac7f-4228-818f-1125cba0810f
Esto ejecutará el comando almacenado en elCMD
variable, lea la primera línea de la salida de ese comando y guárdela en la variable$5
.
Preste especial atención a la declaración close, crucial aquí ya que queremos que AWK cree una nueva instancia del comando externo cada vez que ejecuta elCMD | getline
declaración. Sin la declaración de cierre, AWK intentaría leer varias líneas de salida de la misma instancia de comando.
23. Invocar comandos generados dinámicamente
Los comandos en AWK son simplemente cadenas sin nada especial. Es el operador de tubería el que desencadena la ejecución de programas externos. Entonces, si lo necesita, puede construir dinámicamente comandos complejos arbitrarios utilizando las funciones y operadores de manipulación de cadenas de AWK.
awk '+$1 { cmd = sprintf(FMT, $2); cmd | getline $2; close(cmd); print }' FMT='date -I -d "%s"' FS=, file
99 2018-06-01 sylvain team:::admin
52 2018-12-01 sonia team
52 2018-12-01 sonia team
25 2019-01-01 sonia team
10 2019-01-01 sylvain team:::admin
8 2018-06-12 öle team:support
17 2019-04-05 abhishek guest
Ya hemos conocido aprintf
función.sprintf
es muy similar pero devolverá la cadena construida en lugar de enviarla a la salida.
24. Unir datos
Para mostrarte el propósito de la declaración de cierre, te dejo probar el último ejemplo:
awk '+$1 { CMD | getline $5; print }' CMD='od -vAn -w4 -t x /dev/urandom' FS=, file
99 01 jun 2018 sylvain team:::admin 1e2a4f52
52 01 dec 2018 sonia team c23d4b65
52 01 dec 2018 sonia team 347489e5
25 01 jan 2019 sonia team ba985e55
10 01 jan 2019 sylvain team:::admin 81e9a01c
8 12 jun 2018 öle team:support 4535ba30
17 05 apr 2019 abhishek guest 80a60ec8
A diferencia del ejemplo que utiliza eluuid
comando anterior, aquí solo hay una instancia deod
se lanza mientras se ejecuta el programa AWK, y al procesar cada registro, leemos una línea más de la salida de ese mismo proceso.
Conclusión
Ese recorrido rápido por AWK ciertamente no puede reemplazar un curso o tutorial completo sobre esa herramienta. Sin embargo, para aquellos de ustedes que no estaban familiarizados con él, espero que les haya dado suficientes ideas para que puedan agregar AWK inmediatamente a su caja de herramientas.
Por otro lado, si ya eras un aficionado a AWK, es posible que hayas encontrado aquí algunos trucos que puedes utilizar para ser más eficiente o simplemente para impresionar a tus amigos.
Sin embargo, no pretendo ser exhaustivo. Entonces, en todos los casos, ¡no dudes en compartir tu frase favorita de AWK o cualquier otro consejo de AWK usando la sección de comentarios a continuación!