AWK |
4.14.1 Que es awk y para que se usa
La palabra 'awk' se usa tanto para referirse a un lenguaje de manipulación de ficheros de datos como para referirse a
su interprete.(Para los que se extrañen por ese peculiar nombre , este proviene de sus creadores originales
Alfred Aho, Peter Weinberger y Brian Kernighan).
Dado que los SO tipo Unix incluido Linux acostumbran con mucha frecuencia a usar ficheros de configuración del sistema
en formatos de de texto perfectamente legibles y editables se diseñó un lenguaje para poder procesar este tipo de
ficheros de datos en formato de texto.
Cuando un programa necesita una velocidad muy alta para acceder a unos datos o para modificarlos se utilizan estructuras
de datos más sofisticadas.
En muchos otros casos un fichero de configuración será accedido de forma muy ocasional, y resulta más interesante usar
un formato de texto sencillo. Por ejemplo hay ficheros de configuración que solo se usan durante la carga de un
programa. Algunos de estos programas suelen cargarse una sola vez mientras arranca el sistema y luego en condiciones
normales permanecen arrancados todo el tiempo.
'awk' nació en 1978 como un lenguaje pequeño y sencillo pero desde entonces ha evolucionado mucho y en la
actualidad se puede afirmar que es un lenguaje muy potente y versátil.
'awk' es un complemento muy bueno para su uso con shell-script. Nos vamos a centrar en el procesamiento de datos en
los cuales cada línea estará estructurada en campos. Estos campos estarán delimitados entre si por algún carácter o por
alguna secuencia especial de caracteres especialmente reservado para ello. Esta secuencia será el delimitador de
campos y no debe aparecer en el interior de ningún campo. Cada línea equivale a un registro.
La mayoría de las bases de datos, y hojas de cálculo permiten volcar los datos en formato de texto para poder ser
exportados entre distintas bases de datos. Estas salidas se pueden procesar fácilmente mediante 'awk'. También se
puede usar 'awk' con la salida de diversos programas. Esto permite entre otras cosas usar 'awk' para acoplar
una salida de un programa con la entrada de otro que necesite un formato muy distinto.
'awk' suele estar instalado en la mayoría de los sistemas ya que su uso suele ser necesario. Por eso en Linux
suele encontrarse entre los paquetes básicos del sistema en todas las distribuciones.
Se puede usar de varias formas. Tenemos que pasar a 'awk' el código del programa, y los datos. El primero se puede pasar
bien como argumento o indicando -f nombre del fichero que contiene el código del programa. La entrada se puede
pasar dando el nombre del fichero de entrada como último argumento o en caso contrario lo tomará por la entrada estándar.
$ ## Generamos en /tmp un par de ficheros $ echo -e "\n" > /tmp/echo.out $ echo '{ print "Hola mundo" }' > /tmp/ejemplo1.awk $ ## Ejecutaremos el mismo programa de 4 formas distintas $ echo -e "\n" | awk '{ print "Hola mundo" }' Hola mundo Hola mundo $ awk '{ print "Hola mundo" }' /tmp/echo.out Hola mundo Hola mundo $ echo -e "\n" | awk -f /tmp/ejemplo1.awk Hola mundo Hola mundo $ awk -f /tmp/ejemplo1.awk /tmp/echo.out Hola mundo Hola mundo |
$ whereis awk /usr/bin/awk |
$ echo '#!/usr/bin/awk -f' > /tmp/ejemplo2.awk $ echo '{ print "Hola mundo" }' >> /tmp/ejemplo2.awk $ chmod +x /tmp/ejemplo2.awk $ echo -e "\n" | /tmp/ejemplo2.awk Hola mundo Hola mundo |
4.14.3 Estructura de un programa awk
Un programa 'awk' puede tener tres secciones distintas.
· Puede incluir una primera parte para que se ejecute antes de procesar ninguna de las líneas de entrada. Se usa para
ello la palabra reservada BEGIN seguida de una o más instrucciones todas ellas englobadas dentro de un par de
corchetes. '{' , '}'.
· Puede incluir una parte central que se procesará entera para cada linea de entrada de datos y que puede tener varios
bloques '{' , '}'. Si uno de estos bloques contiene una expresión regular se procesará solo cuando la línea de entrada
se ajuste al patrón de la expresión regular.
· Puede incluir una parte final que se procesará en último lugar una vez termine la lectura y procesado de todas las
líneas de entrada. Se usa para ello la palabra reservada END seguida de una o más instrucciones todas ellas
englobadas dentro de un par de corchetes. '{' , '}'.
El primer ejemplo que vimos anteriormente ("Hola mundo") solo tenía una de las tres partes. Concretamente era la parte
central ya que no pusimos ninguna de las palabras reservadas BEGIN o END.
Vamos a poner ahora un ejemplo con las tres partes. Edite un fichero con nombre '/tmp/3partes.awk'
BEGIN { print "Erase una vez..." } { print "...y entonces bla, bla, bla ..." } END { print "...y colorín colorado este cuento se ha acabado." } |
$ echo -e "\n\n\n" | awk -f /tmp/3partes.awk Erase una vez...... y entonces bla, bla, bla ...... y entonces bla, bla, bla ...... y entonces bla, bla, bla ...... y entonces bla, bla, bla ...... y colorín colorado este cuento se ha acabado. |
Algunas veces los datos pueden venir con algunas lineas que no interesa procesar o que se deben procesar de forma distinta. Podemos usar una expresión regular delimitada por el carácter '/' para seleccionar una acción especial. Vamos a editar otro ejemplo que llamaremos '/tmp/expreg.awk':
BEGIN { print "Erase una vez..." } /^$/ { print "Linea vacía" } /[0-9]+/ { print "Tiene un número" } /\.$/ { print "Termina con punto" } # Esto es un comentario { print "--------------------------------------" } END { print "...y colorín colorado este cuento se ha acabado." } |
Línea número 1. Línea número 2 .... Fin de los datos |
$ awk -f /tmp/expreg.awk /tmp/expreg.dat Erase una vez... Tiene un número Termina con punto -------------------------------------- Tiene un número -------------------------------------- Linea vacía -------------------------------------- Termina con punto -------------------------------------- -------------------------------------- ...y colorín colorado este cuento se ha acabado. |
4.14.5 Delimitadores de campos
No hemos tratado aún los campos de una línea. Una línea que tenga distintos campos debe usar alguna secuencia para
delimitar los campos entre si.
Lo mismo para definir un delimitador que en cualquier otro caso donde se usen cadenas de caracteres podemos encontrarnos
la necesidad de usar caracteres especiales que no pueden ser introducidos directamente. Para ello existen determinadas
secuencias que empiezan por el carácter '\' y que tienen significado especial.
Caracteres de escape
\a Produce un pitido en el terminal
\b Retroceso
\f Salto de página
\n Salto de línea
\r Retorno de carro
\t Tabulador horizontal
\v Tabulador vertical
\dddCarácter representado en octal por 'ddd'
\xhex Carácter representado en hexadecimal por 'hex'
\c Carácter 'c'
El último caso se usa para eliminar el significado especial de un carácter en determinadas circunstancias. Por ejemplo
para usar un '+' o un '-' en una expresión regular usaríamos '\+' o '\-'
Podemos elegir un solo carácter para separar campos. Hay ficheros de configuración como /etc/passwd, /etc/group, que
usan un solo carácter para delimitar los campos. Por ejemplo los dos puntos ':' , el blanco '\ ', la coma ',' el tabulador
'\t' etc...
'awk' permite usar como delimitador más de un carácter. Para ello se asignará a la variable 'FS' una cadena de
caracteres que contenga una expresión regular . Por ejemplo para usar como delimitador el carácter ':' habría que
incluir 'BEGIN { FS = ":" }'
Si no se especifica ningún delimitador se asumirá que los campos estarán delimitados por uno o más blancos o tabuladores
consecutivos lo cual se expresa como "[\ \t]+". El carácter '\' debe usarse para escapar cualquier carácter con
significado especial en una expresión regular y algunos caracteres normales precedidos de '\' se usan para representar
caracteres especiales. '\t' es el tabulador.
En 'awk' se usa $1 para referenciar el campo 1, $2 para referenciar el campo 2, etc... y para referenciar
el registro completo usaremos $0.
Edite el siguiente fichero '/tmp/delim1.awk'
{ print "+", $1, "+", $2, "+", $3, "+", $4, "+" } |
aaa bbb ccc ddd eee 111 222 333 444 |
$ awk -f /tmp/delim1.awk /tmp/delim1.dat + aaa + bbb + ccc + ddd + + 111 + 222 + 333 + 444 + |
{ print "+", $3, "+", $4, "+", $1, "+", $2, "+" } |
$ awk -f /tmp/delim0.awk /tmp/delim1.dat + ccc + ddd + aaa + bbb + + 333 + 444 + 111 + 222 + |
BEGIN { FS = "\ " } { print "+", $1, "+", $2, "+", $3, "+", $4, "+" } |
$ awk -f /tmp/delim2.awk /tmp/delim1.dat + aaa + bbb + ccc + + + 111 + 222 + 333 + 444 + |
BEGIN { FS = "\t" } { print "+", $1, "+", $2, "+", $3, "+", $4, "+" } |
$ awk -f /tmp/delim3.awk /tmp/delim1.dat + aaa bbb ccc + + ddd + + + 111 222 333 444 + + + + |
4.14.6 Selección de registros por campo
Vamos a editar un fichero que simulará la salida de datos obtenida desde una base de datos relacional. Usaremos estos datos en varios ejemplos. Puede corresponder a una contabilidad de un alquiler de un piso. Lo llamaremos 'contabil.dat'.
fecha|concepto|importe ----------+--------+------- 01-01-2004|- | 96 16-12-2004|AGUA | -14650 05-01-2004|LUZ | -15797 24-01-2004|GAS | -34175 27-01-2004|INGRESO | 141200 01-02-2004|MENS | -96092 25-02-2004|LUZ | -12475 01-03-2004|MENS | -96092 06-03-2004|INGRESO | 101300 01-04-2004|MENS | -96092 06-04-2004|AGUA | -15859 07-04-2004|INGRESO | 134000 01-05-2004|MENS | -98975 02-05-2004|LUZ | -11449 09-05-2004|INGRESO | 95000 23-05-2004|GAS | -21428 25-05-2004|GAS | -16452 01-06-2004|MENS | -98975 07-06-2004|INGRESO | 130000 01-07-2004|MENS | -98975 04-07-2004|LUZ | -12403 07-07-2004|AGUA | -5561 10-07-2004|INGRESO | 99000 24-07-2004|GAS | -11948 01-08-2004|MENS | -98975 10-08-2004|INGRESO | 122355 04-09-2004|LUZ | -12168 10-09-2004|INGRESO | 129000 19-09-2004|AGUA | -10529 28-09-2004|GAS | -2620 01-10-2004|MENS | -98975 10-10-2004|INGRESO | 112000 (32 rows)
Lo primero que vemos es que tiene una cabecera de dos líneas inútiles y un final también inútil. Podemos asegurar que las líneas que deseamos procesar cumplirán un patrón de dos números guión dos números guión cuatro números y línea vertical. Vamos a editar un programa que llamaremos 'contabil1.awk'
BEGIN { FS="\|" } /[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ { print NR, ", ", $1, ", ", $2, ", ", $3 } |
3 , 01-01-1999 , - , 96 4 , 16-12-1999 , AGUA , -14650 5 , 05-01-2000 , LUZ , -15797 6 , 24-01-2000 , GAS , -34175 7 , 27-01-2000 , INGRESO , 141200 8 , 01-02-2000 , MENS , -96092 9 , 25-02-2000 , LUZ , -12475 10 , 01-03-2000 , MENS , -96092 11 , 06-03-2000 , INGRESO , 101300 12 , 01-04-2000 , MENS , -96092 13 , 06-04-2000 , AGUA , -15859 14 , 07-04-2000 , INGRESO , 134000 15 , 01-05-2000 , MENS , -98975 16 , 02-05-2000 , LUZ , -11449 17 , 09-05-2000 , INGRESO , 95000 18 , 23-05-2000 , GAS , -21428 19 , 25-05-2000 , GAS , -16452 20 , 01-06-2000 , MENS , -98975 21 , 07-06-2000 , INGRESO , 130000 22 , 01-07-2000 , MENS , -98975 23 , 04-07-2000 , LUZ , -12403 24 , 07-07-2000 , AGUA , -5561 25 , 10-07-2000 , INGRESO , 99000 26 , 24-07-2000 , GAS , -11948 27 , 01-08-2000 , MENS , -98975 28 , 10-08-2000 , INGRESO , 122355 29 , 04-09-2000 , LUZ , -12168 30 , 10-09-2000 , INGRESO , 129000 31 , 19-09-2000 , AGUA , -10529 32 , 28-09-2000 , GAS , -2620 33 , 01-10-2000 , MENS , -98975 34 , 10-10-2000 , INGRESO , 112000
Podemos apreciar varias cosas. NR es una variable del sistema que toma el valor del número de registro que se está procesando. Podemos ver que las dos primeras líneas y la última han sido descartadas. También vemos que las primeras líneas usan un solo dígito para el número de registro y luego usan dos dígitos. Esto hace que las columnas no queden alineadas. Vamos a modificar el programa para que muestre los registros completos ($0) cuando no se cumpla la condición anterior. Para ello editaremos un fichero que llamaremos 'contabdescarte.awk'.
BEGIN { FS="\|" } ! /[0-9][0-9]\-[0-9][0-9]\-[0-9][0-9][0-9][0-9]\|/ { print NR, $0 } |
$ awk -f contabdescarte.awk < contabil.dat 1 fecha|concepto|importe 2 ----------+--------+------- 35 (32 rows) |
4.14.7 Formato de salida con printf
Para imprimir con formato usaremos 'printf' en lugar de 'print'. printf se usa en varios lenguajes.
El primer argumento de esta función debe de ser una cadena de caracteres que contenga el formato de salida deseado para
la salida. Los formatos de cada dato se expresan mediante unas directivas que empiezan con el carácter '%' y
debe de existir en dicha cadena tantas directivas como datos separados por coma a continuación de la cadena de formato.
Hay que tener en cuenta que en 'awk' la concatenación de cadenas se usa poniendo una cadena a continuación de otra separada
por blancos.
Por ejemplo:
# cad = "(unodos)" cad = "uno" "dos" ; cad = "(" cad ")" |
$ echo | awk '{ print "Hola mundo" }' Hola mundo $ echo | awk '{ printf "Hola %s\n", "mundo" }' Hola mundo $ echo | awk '{ printf "#%d#%s#\n", 77, "mundo" }' #77#mundo# $ echo | awk '{ printf "#%10d#%10s#\n", 77, "mundo" }' # 77# mundo# $ echo | awk '{ printf "#%-10d#%-10s#\n", 77, "mundo" }' #77 #mundo # $ echo | awk '{ printf "#%+4d#%+4s#\n", 77, "mundo" }' # +77#mundo# $ echo | awk '{ printf "#%04d#%+4s#\n", 77, "mundo" }' #0077#mundo# $ echo | awk '{ printf "#%010.5f#%E#%g\n", 21.43527923, 21.43527923, 21.43527923 }' #0021.43528#2.143528E+01#21.4353 $ echo | awk '{ printf "#%10.5f#%E#%g\n", 2140000, 2140000, 2140000 }' #2140000.00000#2.140000E+06#2.14e+06 |
4.14.8 Uso de variables operadores y expresiones
En 'awk' podemos usar toda clase de expresiones presentes en cualquier lenguaje. Cualquier identificador que no
corresponda con una palabra reservada se asumirá que es una variable. Para asignar un valor se usa el operador '='
Vamos a editar un fichero que llamaremos 'ejemplexpr.awk' con algunas expresiones aritméticas.
{ contador = 0; # Pone a cero la variable contador contador ++; # Incrementa en 1 la variable contador contador +=10; # Incrementa en 10 la variable contador. contador *=2 # Multiplica por 2 la variable contador print contador contador = ( 10 + 20 ) / 2 ; print contador contador = sqrt ( 25 ) ; # Raiz cuadrada de 25 print contador } |