Introducción a AWK (II)

Por dónde seguir

Este es el segundo post de una seria sobre AWK. El primero lo puedes encontrar aquí. Si no le has echado un vistazo te recomiendo hacerlo. Sigamos ahora por donde lo dejamos.

Operaciones matemáticas con varias columnas

Una de las últimas cosas que hicimos en el post anterior sobre AWK fue sumar una columna para ver el total. Lo cierto es que hay muchas más posibilidades. Teniendo el mismo archivo que antes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ cat inventario.txt 
Ene  13  25  15 115
Feb  15  32  24 226
Mar  15  24  34 228
Abr  31  52  63 420
May  16  34  29 208
Jun  31  42  75 492
Jul  24  34  67 436
Ago  15  34  47 316
Sep  13  55  37 277
Oct  29  54  68 525
Nov  20  87  82 577
Dic  17  35  61 401
 
Ene  21  36  64 620
Feb  26  58  80 652
Mar  24  75  70 495
Abr  21  70  74 514

Vamos ahora a sumar lo obtenido en los meses de Enero. Recordemos que la última columna se refiere a los euros que obtenemos ese mes.
Así que tendríamos que hacer algo como: “Si estamos en Enero entonces súmalo al total. Cuando terminemos con todos los ‘Eneros’ entonces imprimimos el resultado”. Esto en AWK sería:

1
awk '$1=="Ene" { sum=sum+$5 }; END {print "Hemos ganado " sum "€ en total con todos los meses de Enero"}' inventario.txt

Como vemos ahora la condición es que estemos en el mes de Enero (“Ene”), si es así lo sumamos. Al final, con END, indicamos la salida. Podemos reducir la expresión de la suma, con el operador “+=”, quedando así:

1
awk '$1=="Ene" { sum += $5 }; END {print "Hemos ganado " sum "€ en total con todos los meses de Enero"}' inventario.txt

Esto, evidentemente produce la misma salida:

Hemos ganado 735€ en total con todos los meses de Enero

Variables Especiales o built-in

Existen una serie de variables “especiales” o como las queramos llamar, que suponen una tremenda ayuda a la hora de usar AWK. En realidad ya hemos utilizado una en el anterior post, “FNR”, que indicaba el número de línea actual en el que estamos. Pero hay más.

FS (Field Separator)

Esta variable permite decir que vamos a usar como separación, por defecto su valor es ” “, como podemos ver en los archivos que hemos procesado hasta ahora, los campos estaban separados por un espacio. Pero este valor puede cambiar, simplemente haciendo una asignación. Imaginemos que tenemos el siguiente archivo:

1
2
3
4
$ cat contactos.txt
Carlos García,Calle Nueva N 27,Barcelona, 08124,carlos@gmail.com
Rocío López,Calle Vieja N 2,Madrid,28562,rocio@google.com
Patricia Díaz,Calle Secreta N 11,Cádiz,72723,patricia@hotmail.com

En él se puede observar que hay datos de contactos, separados por comas. Y como ya sabemos el espacio es el separador que nos vale.

Imaginemos que vamos a hacer una lista de correo, y queremos extraer los correos de nuestros contactos. Se puede plantear la opción de modificar el archivo y cambiar las comas por espacios, pero lo cierto es que ya hay espacios, y si luego vamos a referirnos a la dirección, en $2, solo obtendríamos “Calle”, puesto que hay más espacios dentro de ese campo.

Así que la opción pasa por asignar el valor de “,” a la variable FS. Esto es algo que tenemos que hacer antes que nada, y para ello tenemos “BEGIN”. La instrucción quedaría así:

1
awk 'BEGIN { FS = "," } ; { print $5 }' ./contactos.txt

Nótese que aquí $5 se refiere al 5º campo entre comas que tenemos por línea, así, la salida sería:

carlos@gmail.com
rocio@google.com
patricia@hotmail.com

Aquí hay un detalle, si en una línea tenemos 2 espacios seguidos, sería como tener “,,” es decir, una columna vacía, en cambio, si hay 2 espacios AWK no lo interpreta como un campo vacío entre medias, y se lo salta. Esto es por el lenguaje y para poder emular el mismo comportamiento necesitamos expresiones regulares, algo que veremos más adelante con detenimiento.

NF (Number of Fields)

Esta variable contiene el número de campos en la línea actual. Aprovechando el archivo anterior, para saber con cuántos campos estamos tratando:

1
awk 'BEGIN { FS = "," } ; { print NF }' ./contactos.txt

Y su salida será:

5
5
5

Es decir, tenemos 5 campos por línea, en todas las líneas. Se puede aprovechar el contenido de esta variable para referirse al último campo. Puesto que, de la misma forma que:


5 = 5
$5 es el contenido del campo 5

Con NF, pasa lo mismo:


NF = 5
$NF = contenido del campo 5.

Así que aprovechando esto, tendremos:

1
awk 'BEGIN { FS = "," } ; { print $NF }' ./contactos.txt

Y la salida sería exactamente la misma que si hubiéramos puesto $5.

carlos@gmail.com
rocio@google.com
patricia@hotmail.com

Así que usando NF, no nos tenemos que preocupar de contar los campos, mientras que tengamos claro que lo que queremos obtener está en el último campo de cada línea.

NR (Number of Record)

NR contiene el número de registro en el que estamos, entendiendo registro como línea (aunque podría ser otra cosa, pero lo veremos más adelante).

Es importante que sepamos bien como es el archivo con el que vamos a trabajar, aquí estamos planteando ejemplos de archivos de pocas líneas pero con un archivo de 1000 o 2000 líneas, como puede pasar perfectamente en la realidad, no podemos ver a mano si todos los campos están bien y todo es uniforme.

Así si por ejemplo queremos ver que todas las líneas(registros) de un archivo tienen el mismo número de campos. Lo primero que debemos saber es como va a ser el informe que vamos a procesar, si le hemos pedido a los administrativos que hagan 3 columnas por línea, lo último que van a hacer va a ser eso, así que veamos como mirar si está mal.

El planteamiento es: si esperamos 3 y uno tiene algo distinto, lo decimos.

El archivo es este

1
2
3
4
5
$ cat informe.txt
Campo1 Campo2 Campo3
Campo1 Campo2 Campo3 Campo4
Campo1 Campo2 Campo3
Campo1 Campo2 Campo3

Como se puede ver, esperábamos tres campos y en la línea 2, hay 4. Nuestra instrucción quedaría:

1
2
3
4
awk 'BEGIN { n = 3 }; { 
	if (n != NF) 
		{print "Error en la linea " NR ", tiene " NF " campos."} 
}' ./informe.txt

Y su salida:

Error en la linea 2, tiene 4 campos.

Si nos fijamos, ahora no nos interesa el contenido del registro número NF($NF), sino el valor en sí, es decir, el número de campos por línea, por ello usamos NF simplemente, que contendrá 3, 4, 3 y 3, según avance la ejecución. Además, NR nos aporta más información sobre el error, dicíendonos en que línea hay más campos de los esperados.

Dos cosas más a destacar es el uso del if y que existen varias líneas, en el programa. Respecto al uso del if, es como un if en el resto de lenguajes imperativos, con la estructura:

1
2
3
4
if (condicion)
   {accion}
else
   {accion2}

Y que el programa tenga varias líneas no es problema para el shell. Se interpreta como si estuviéramos introduciendo todo en una línea. Recordemos que las comillas simples al principio y al final del programa awk, no pertenecen propiamente al lenguaje, es una forma que tenemos para decirle al shell “lo que está entre las comillas simples no lo interpretes para ti, solo pásaselo a AWK”.

Conclusión

Hasta aquí llega esta parte, en la próxima veremos que una línea no tiene que ser un registro necesariamente, y profundizaremos más en AWK, con el uso de otras variables especiales y más funcionalidades que podamos darle a este lenguaje.

Fotografía de Maschinenraum (Flickr)