La gran potencia de AWK en Linux

Publicado el 24 junio 2016 por Drassill
Revisando antiguos artículos relacionados con bash, he visto que más de una vez he mencionado un recurso del cual no he dado explicaciones, un parámetro extraordinariamente potente que, en mi opinión, merece ser mencionado y explicado para que todo el mundo lo entienda debidamente. Si no habéis realizado ningún script en bash, probablemente nunca hayáis tenido que recurrir a él, pero en caso contrario, es muy probable que lo hayáis usado, en mayor o menor medida. Se trata de awk, un recurso de programación de bash que puede sacarnos de más de un escollo; un recurso que se podría denominarse como parte recurso de ayuda para bash y como parte lenguaje de programación.

El uso más común de todos y el que más de una vez he usado en los scripts que he mostrado en este blog, es el filtrado de un resultado por columnas. A veces el parámetro cut se nos puede quedar corto y necesitamos algo más potente que nos pueda filtrar las columnas deseadas a nuestro gusto; para ello habría que acompañar el comando que queramos por la sintaxis: |awk '{print $número_columna $número_columna2}'.  El número de columnas que podemos mostrar es equivalente al número de columnas disponibles, con lo que podemos escoger mostrar únicamente una columna, dos, o las que queramos, siempre y cuando dicho número de columna sea válido. Para tener una visión clara de esto, lo mejor es plasmarlo en un pequeño ejemplo. Supongamos que queremos mostrar detalladamente el contenido del directorio etc; para simplificar el resultado únicamente mostraríamos las 10 primeras líneas. Esto lo lograríamos mediante el comando:
ls-l/etc/|head-n10
Esto mostraría algo como esto:

Lo malo de esto es que hay bastante información "innecesaria" o molesta que no nos interesa en estos momentos. ¿Qué nos importan los permisos de los ficheros y directorios? ¿Acaso queremos saber a qué usuario y grupo pertenecen? Para esta situación en concreto solo nos interesaría la última fecha de modificación de las carpetas/ficheros junto con, obviamente, el nombre de éstos. Es decir que solamente querremos obtener el resultado de las columnas 6,7,8 y 9. Esto lo lograríamos mediante el siguiente comando:
ls-l/etc/|head-n10|awk $'{print $6 " " $7 " " $8 " " $9}'

Hasta aquí diríamos que sería el uso más habitual y básico de AWK, un uso que no debe de ser en absoluto desdeñado pues a mí, personalmente, me ha sido de gran ayuda en muchísimas ocasiones. Pero la verdad es que se puede ir mucho más allá de este uso tan básico. El uso del número de posición, en realidad hace referencia a unas variables, variables que van mucho más allá de estos "simples" números. Hay variables que pueden ser creadas por awk, pero hay un buen número de variables que ya posee de forma implícita awk; algunas de éstas serían:
  • NS: Esta variable haría referencia al número de columnas que ofrece un resultado; ya sea el resultado de un comando o una variable. Si por ejemplo, usásemos un |awk '{print $NF}', mostraríamos siempre la última columna, pues el número de la última columna sería equivalente al número de columnas. 
  • NR: A veces un resultado puede ofrecer varias filas; siendo difícil conocer a qué número de fila pertenece cada resultado; gracias al uso de |awk '{print NR}' (sin $), mostraremos el número de línea de cada resultado.
  • OFS: En el segundo ejemplo que he mostrado anteriormente, habéis visto que he usado " " para serparar cada columna entre sí con un espacio; podemos optar por hacer uso de la variable OFS para hacer exactamente lo mismo si así lo deseamos. Ejemplo: ls -l |awk $'{print $6 OFS $7 OFS $8 OFS $9}'.
  • ORS: Si en vez de separar las columnas con espacios en blanco, separamos éstas mostrando el resultado en diferentes líneas, haríamos uso de la variable ORS; algo como: ls -l |awk $'{print $6 ORS $7 ORS $8 ORS $9}'.

Existen muchas más variables, pero con esto os podéis hacer una idea de la potencia que awk ofrece mucha más potencia de lo que parece y que existen muchas más variables que las numéricas. 
AWK también permite realizar operaciones aritméticas; es decir que permite hacer operaciones matemáticas sobre los resultados obtenidos dentro de {}. Esto puede resultarnos útil cuando queremos convertir un valor numérico a otro más "legible" o simplemente queremos efectuar una operación sobre él... 
Un simple ejemplo de esto sería la contabilización de usuarios conectados... El comando uptime muestra, además del tiempo que lleva encendido el equipo, el número de usuarios logueados en el equipo; ya sean usuarios que han iniciado sesión local o remotamente en el susodicho... Dicha contabilización incluye al usuario que ha introducido el comando, con lo que aún cuando no haya absolutamente nadie más logueado, siempre mostrará un valor mínimo de 1; gracias a awk, podemos restarle 1 a dicho resultado para que el comando nos diga únicamente el número de usuarios ajenos conectados. Esto se plasmaría con este sencillo comando:
uptime|awk'{print $4-1}'
Pero tal y como he dicho antes, awk también puede admitir bloques de código; es decir que permite realizar ciertas instrucciones parecidas a las usadas en el desarrollo de otros lenguajes... Por ejemplo awk permite realizar bucles, introducir condicionales, etc... tal y como se hace en otros lenguajes de programación... Aquí uno puede jugar con los datos como quiera, pero he aquí unos pequeños ejemplos de la potencia de estos bloques de código sería esta muestra.
Esta sería una pequeña prueba de concepto de lo que podemos lograr mediante el uso de condicionales (if else)... Gracias a dichas condiciones he "transformado" una salida que tendría que haber mostrado 1 2 3 4 en: Hola que tal estas? 
  1. root@debian:~# echo "1 2 3 4" |awk '{if ($1 == 1){$1 = "Hola"} if ($2 >= 2) {$2 = "que"} if ($3 <= 3) {$3 = "tal"} if ($4 != 4) {$4 = 4} else {$4 = "estas?"} print $1 OFS $2 OFS $3 OFS $4}'
  2. Hola que tal estas?

Obviamente no todo se reduce a condicionales; también podemos hacer uso de bucles for, tal y como en este ejemplo; ejemplo en el que si estáis familiarizados con lenguajes de programación tales como C, probablemente encontréis similitudes:
  1. root@debian:~# echo "Bucle" |awk '{for ( x = 1; x <= 10; x++ ) {print $1,x} }'
  2. Bucle 1
  3. Bucle 2
  4. Bucle 3
  5. Bucle 4
  6. Bucle 5
  7. Bucle 6
  8. Bucle 7
  9. Bucle 8
  10. Bucle 9
  11. Bucle 10

Como podéis ver awk dista mucho de ser un simple recurso de filtrado de resultados; es toda una herramienta que nos permite realizar desde simples operaciones a ejecuciones de códigos de programación, siendo una utilidad muy a tener en cuenta por cualquiera que le guste sacar todo el partido posible a las terminales de Linux. Si deseáis profundizar más en esta utilidad, especialmente en lo referente a las variables predeterminadas de awk, os recomiendo que hagáis uso de man, pues sin duda encontrareis dicha información de utilidad.
Espero que os haya resultado útil.
Saludos.