El shell de linux: Awk | Algo de Linux

martes, 4 de marzo de 2008

El shell de linux: Awk

Awk busca ciertos patrones en la entrada, y la procesa de la manera especificada. Awk tiene una gran funcionalidad, pero esta mayor funcionalidad tiene su coste reflejado en una mayor complejidad del lenguaje.

awk dispone de un lenguaje completo, sintácticamente similar a C que tiene una gran potencia a la hora de reconocer patrones en la entrada, ya que permite especificar combinaciones de expresiones regulares.

Además, no es necesario procesar la entrada línea a línea. Awk permite escoger el carácter que indica el fin de un registro y procesar la entrada de registro en registro (En el lenguaje awk, un ‘registro’ es el equivalente a una ‘línea’).
Awk separa automáticamente cada registro en campos que pueden utilizarse individualmente.

Por defecto, un registro es una línea del fichero, lo que significa que el separador de registros es ‘\n’.

Por defecto, un campo es todo aquello que esté separado por espacios en blanco, es decir, una palabra. El separador de campos por defecto es '[ \t]' (espacio y tabulador).

Una posible sintaxis de awk sería:
awk [fichero_entrada]
Un programa de awk es una secuencia de sentencias patrón-acción, con un formato determinado, en el que las acciones se ejecutarán si en el registro actual se cumple el patrón.
El formato es el siguiente:
patrón {accion}
Suele ser necesario encerrar los programas de awk entre comillas, para evitar que el shell las interprete como caracteres especiales.

Hay que tener en cuenta dos cosas:
  • Si no hay patrón, las acciones se ejecutan en todos los registros.
  • Si no hay acciones, lo que se hace es ejecutar la acción por defecto, que es copiar el registro en la salida estándar.
Veamos un par de ejemplos o tres de uso de awk:

* Mostramos el nombre de usuario de todos los usuarios logueados en la máquina:
who|awk '{print $1}'
* Borramos todas las líneas vacías de un fichero:
awk '!/^$/ {}' fichero
* Mostramos el nombre de usuario y el intérprete que usa:
awk 'BEGIN {FS=":"}; {print $1,$NF | "sort"}' /etc/passwd
* Mostramos el nombre completo del usuario y su login:
awk 'BEGIN {FS=":"}; {print $1,$5 | "sort"}' /etc/passwd

Variables
Como ya hemos dicho, awk dispone de un lenguaje completo, y, como cualquier otro lenguaje, dispone de variables. Las variables pueden ser de dos tipos:

  • Variables predefinidas.
  • Variables definidas por el usuario.
Veamos cuales son las variables predefinidas:
  • FS (Field separator): Permite indicar a awk cuál es el caracter que separa los campos. Por defecto es el espacio. La forma de indicar a awk el caracter de separación de campos es la siguiente: FS = “caracter”. Por ejemplo: FS = ",". Si hacemos FS = "", estamos indicando a awk que cada carácter es un campo.
  • NF (Number of fields): Contiene el número total de campos que contiene el registro que se está leyendo en cada momento.
  • RS (Record separator): Contiene el carácter que indica a awk en qué punto del archivo acaba un registro y empieza el siguiente. Por defecto es el caracter “\n”.
  • NR (Number of record): Contiene el número de orden del registro que se está procesando en cada momento.
  • OFS (Output FS): La instrucción print inserta en la salida un carácter de separación cada vez que aparece una coma en el código. Mediante OFS, podemos indicar a awk que separe los campos mediante el separador que le indiquemos. Por ejemplo: OFS = ";"

En cuanto a las variables definidas por el usuario, se crean directamente al hacer referencia a ellas en expresiones.

Las variables pueden ser:

  • Escalares: Almacenan un solo valor.
  • Vectoriales: Como vectores o arrays. En awk, se pueden crear arrays asociativos, dado que el lenguaje nos permite usar una cadena como índice del array. Para referirnos a un elemento dentro de un array, lo haremos: nombre[ subíndice ].

Campos de entrada
En Awk se considera cada registro del archivo de entrada como una sucesión de campos delimitados por un carácter dado. Este carácter es, por defecto, el espacio.
En cualquier caso, podemos indicar a awk que considere otro carácter como separador de campos mediante la opción FS, tal y como podemos ver en ejemplos anteriores.

Cada uno de estos campos se numera de forma correlativa, según su posición en la línea (o registro), de la siguiente manera: $1, $2, $3, ... Además, también podemos referirnos a la línea entera con $0.

Por otra parte, se puede forzar a procesar una línea carácter a carácter, dejando la variable “separador de campos” FS sin contenido. Si hacemos ésto, en $1 se tendrá el primer carácter de la línea, en $2 el segundo, etc.

El otro día me encontré con el siguiente ejercicio:

Hacer un script que visualice la lista de usuarios que se encuentran conectados en el sistema, mediante el siguiente formato: nº orden -- nombre usuario, totalizando el nº de usuarios. Ejemplo:

1 -- root
2 -- df01
3 -- df02


Utilizando awk, la solución es tan sencilla como la siguiente:
#/bin/bash
who -u|awk 'BEGIN { i=0 } { i+=1; print i,"-",$1 } END { print "Total usuarios " i }'

Estructura básica de un programa con awk
Para entender fácilmente la estructura de un programa con awk, podemos fijarnos en el ejemplo anterior, en el que tenemos tres bloques:
  • BEGIN { i=0 }
  • { i+=1; print i,"-",$1 }
  • END { print "Total usuarios " i }
El primero, se ejecuta al inicio. En este caso, hemos utilizado el bloque BEGIN para inicializar la variable i con valor 0.
El segundo bloque se ejecuta para cada patrón (o registro, como queramos llamarlo). En este caso, incrementa el valor de i, y lo muestra por pantalla seguido de un guión y el campo nº 1 (que en este ejemplo es el login del usuario) Como no hemos indicado ningún separador de campo, se toma por defecto el espacio.
El tercer bloque se ejecuta al final. ¿Y qué hace el bloque en el ejemplo? Imprimir el número total de usuarios.

Awk puede servirnos muy bien para procesar ficheros de texto, extraídos de bases de datos, en los que tenemos registros con campos de datos.

Como ya hemos dicho, awk dispone de un lenguaje completo, con sentencias, condicionales, bucles, estructuras ... Una sentencia que puede sernos de utilidad en el procesamiento de ficheros, es el if. Veamos un ejemplo usando esta sentencia:

awk '{ if (x % 2 == 0) print "x is even"; else print "x is odd" }'

3 comentarios:

javi dijo...

Buen aporte ;)

Anónimo dijo...

Por fin he podido usar
AWK. ¡Muchas gracias!

Tauro Mx dijo...

Gracias brother, excelente introducción rápida.