27 de Septiembre, 2005

Antes de Perl: AWK

Alguna vez he usado este lenguaje de programación en mis scripts shell, y parece que, sin hacer gran cosa, algún que otro lector no está muy familiarizado con esta herramienta :D.

Tampoco voy a hacer un tutorial, pero sí dar algunas pistas que son, ni más ni menos, el uso limitado que hago de este lenguaje :).

Un Auk
Un simpático Auk del Atlántico

Dice la wikipedia sobre AWK que es un lenguaje de propósito general (uhm), llamado así por los apellidos de sus creadores (Aho, Weinberger y Kernighan, a finales de los 70). Como posteriormente se consideró que resultaba feo esta forma de poner nombre a un lenguaje, se empezó a usar la imagen del Auk, un ave similar en algunos aspectos al pingüino.

Es una de las razones, y fuente de frustraciones, por las cuales Larry Wall desarrolló el lenguaje Perl (porque se le quedaba corto). Pero como digo en el título de la anotación, con AWK podemos hacer unas cuantas cosas antes de recurrir a Perl ;).

El uso más habitual y básico del lenguaje es para seleccionar y trabajar con elementos de la salida de un programa para usarla en nuestro script. Por ejemplo, los PID de los procesos del usuario _ups:

$ ps uU _ups | awk ' { print $2 } '
PID
2925
18052
28628

Discriminamos el resto de la salida del comando ps para quedarnos con lo que nos interesa. Pero vamos a partir de un fragmento del registro de Apache para explicar algunas cosas:

$ tail -50 /var/www/logs/access_log > log

AWK procesa los ficheros linea por linea (el separador lo indica RS, y por defecto es el salto de linea), y almacena en variables los siguientes elementos:

  • $0: Todo el registro (linea por defecto).
  • $1: El primer elemento usando FS como separador (espacio en blanco por defecto).
  • $2: El segundo elemento.
  • $3: El tercer elemento, así hasta NF elementos.

Por ejemplo, para el log que tenemos:

$  awk '{ print "Con \"" FS "\" hay", NF "en esta linea" }' < log
Con " " hay 20 en esta linea
Con " " hay 13 en esta linea
Con " " hay 20 en esta linea
...
Con " " hay 21 en esta linea
Con " " hay 21 en esta linea
Con " " hay 21 en esta linea

$ awk '{ print "Elemento 1", $1 }' < log
Elemento 1 201.27.216.*
Elemento 1 66.249.71.*
Elemento 1 201.27.216.*
...
Elemento 1 62.14.59.*
Elemento 1 62.14.59.*
Elemento 1 62.14.59.*

Aunque se puede indicar a AWK que ejecute un fichero completo, es habitual usarlo en una una sola linea de comando, empleando las comillas simples para delimitar el código a ejecutar.

Una función interesante es print que ya se ve como funciona en los ejemplos: se indican los elementos a mostrar en la salida estándar, usando la coma como separador si queremos que los elementos no aparezcan pegados. Se pueden escapar caracteres especiales con la barra a la izquierda, como en C (he escapado dos veces las comillas dobles en el ejemplo).

El siguiente caso de uso interesante es la forma:

patrón { instrucciones }

Donde patrón es una expresión regular.

Si no ponemos la segunda parte e indicamos solo el patrón, se muestran las lineas donde el patrón tiene coincidencia (como un grep, vamos).

Volvamos al log de Apache y vamos a ver qué entradas hacen referencia al fichero blackshell.rss. Para ello empleamos la cadena de la petición HTTP correspondiente:

$ awk ' /GET\ \/blackshell\.rss\ HTTP/ { print $1 } ' < log
80.58.21.*
80.28.49.*
83.44.121.*
...
80.28.49.*
82.144.6.*
216.109.121.*

Los patrones se indican entre dos / y, al ser expresiones regulares, necesitamos escapar algunos valores (los espacios, la propia barra a la derecha del camino al fichero blackshell.rss, los puntos, etc.).

La expresiones regulares son como se indican en re_format(7), así que podemos variar el patrón anterior para que también contemple los ficheros con extensión rdf fácilmente cambiando rss por (rss|rdf) (esto es rss o rdf).

Los patrones también pueden ser expresiones condicionales, que en evaluarse a cierto, conllevarán la ejecución de las sentencias entre llaves. Volviendo al primer ejemplo:

$ ps uU _ups | awk ' $2 != "PID" { print $2 } '
2925
18052
28628

Solo se muestra el segundo elemento cuando es distinto de la cadena PID, así que eliminamos la cabecera que genera ps en su salida.

Por último, y para no extenderme, comentar que hay dos patrones especiales: BEGIN y END. El primero se procesa al comenzar la ejecución y el segundo al terminarla. Por ejemplo (que ya lo hize, pero más sucio y menos eficiente):

$ awk ' BEGIN { rss=0; rdf=0 }
/GET \/blackshell\.rss\ HTTP/ { rss++ }
/GET \/blackshell\.rdf\ HTTP/ { rdf++ }
END { print "RSS:", rss "\nRDF:", rdf } ' < log
RSS: 11
RDF: 17

He puesto un salto de linea entre pares patrón/instrucciones para clarificar. También podríamos poner el contenido entre comillas simples en un fichero y llamar a AWK con la opción -f fichero (sin usar entonces las comillas simples, claro).

Primero indicamos que al comenzar la ejecución las variables rss y rdf valen cero (notar el punto y coma para separar las dos instrucciones, hasta ahora solo habíamos usado una instrucción).

Luego hay un patrón para los ficheros rss y otro para los ficheros rdf, incrementando cada uno de ellos la variable correspondiente.

Finalmente mostramos en la salida estándar un resumen de los valores de ambas variables.

Además hay unas cuantas funciones internas, sentencias de control de flujo, bucles, etc; lo que lo convierte en un lenguaje bastante completo para realizar tareas sobre ficheros de texto (principalmente, no me atrevería a suscribir lo de propósito general). Recomiendo echar un ojo a la página del manual de la versión que tengamos instalada (hay varios dialectos más o menos compatibles entre sí).

He arreglado un poco un script que tenía por ahí para generar una página web desde los leases de un servicio DHCP: dhcp.awk y un ejemplo de salida, como un script un poco más complejo realizado con AWK :).

Anotación por Juan J. Martínez, clasificada en: scripting, awk, unix.

Hay 3 comentarios

Gravatar

Como siempre encuentro cosillas de utilidad en este blog y ya tengo tu script usando AWK en funcionamiento.. :)

por ariel, en 2005-09-28 03:20:15

Gravatar

No lo he tenido en producción nunca, en elxwifi usamos un script en perl que viene a hacer lo mismo (más colorear los leases activos). Si encuentras un fallo, mándame un mail :D

Este tipo de scripts son útiles en servidores DHCP que dan servicio a redes públicas, para monitorizar un poco las estaciones que conectan y tal. Se pone en cron cada x minutos para que deje el HTML en el servidor web y a correr.

por Juanjo, en 2005-09-28 10:38:50

Gravatar

La verdad, el awk tiene su gracia; lo que ocurre es que en cuanto te sales del procesamiento de línea deja de ser útil... pera eso está el Perl.

por JJ, en 2005-09-30 12:13:41

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.

Algunas anotaciones relacionadas: