Gentoo Logo

Renuncia de responsabilidad: La versión original de este artículo fue publicada por IBM developerWorks y es propiedad de Westtech Information Services. Este documento es una versión actualizada del artículo original y contiene mejoras introducidas por el Equipo de Documentación de Gentoo.
Este documento carece de soporte activo.


Awk mediante ejemplos, Parte 2

Contenido:

1.  Registros, bucles y matrices

Registros compuestos de múltiples líneas

Awk es una herramienta excelente para leer y procesar datos estructurados, tales como el contenido del fichero de sistema /etc/passwd. /etc/passwd contiene la base de datos de usuarios en UNIX, y es un fichero de texto con campos delimitados por el signo de dos puntos. Además contiene información importante como las cuentas de usuario existentes y los números de ID de los usuarios, entre otras cosas. En mi artículo anterior, mostré como awk puede procesar este fichero de forma rápida. Todo lo que se necesitaba era establecer la variables de separador de campos FS al carácter ":".

Al establecer la variable FS de forma correcta, awk puede ser configurado para interpretar casi cualquier bloque de datos estructurado, siempre que haya un registro por línea. Sin embargo, FS por sí sola no nos solucionará el problema si necesitamos procesar registros que ocupen varias líneas. En estas situaciones, también necesitaremos modificar la variable RS, que es el separador de registro. Esta variable le dice a awk cuando se acaba un registro y cuando comienza el siguiente.

Como ejemplo, podemos considerar la tarea de procesar la lista de direcciones de los participantes en el programa federal de protección de testigos:

Listado de Código 1.1: Registros de ejemplo para la lista del programa federal de protección de testigos

Jimmy the Weasel
100 Pleasant Drive
San Francisco, CA 12345

Big Tony
200 Incognito Ave.
Suburbia, WA 67890

Lo ideal sería que awk reconociera cada grupo de tres líneas como un registro, en lugar de reconocer cada línea como un registro individual. Nuestro código sería mucho más simple si awk reconociera la primera línea del registro como el primer campo ($1), la dirección como el segundo campo ($2), y la ciudad, estado y código postal como el tercer campo ($3). Y eso lo conseguimos con el código siguiente:

Listado de Código 1.2: Extrayendo un campo de la dirección

BEGIN {
    FS="\n"
    RS=""
}

Al establecer FS a "\n" le decimos a awk que cada campo aparecerá en su propia línea. Estableciendo RS a "" le decimos a awk que cada registro está separado por una línea en blanco. Una vez que awk sabe como la entrada está formateada, puede realizar todo el trabajo por sí solo, y el resto del guión es simple. Veamos como quedaría un guión completo para procesar esta lista de direcciones e imprimir cada registro completo en una sola línea, en campos separados por comas.

Listado de Código 1.3: Guión completo

BEGIN {
    FS="\n"
    RS=""
}
{ print $1 ", " $2 ", " $3 }

Si el guión se salvó como address.awk, y la información sobre las direcciones está en un fichero llamado address.txt, puedes ejecutar este guión escribiendo awk -f address.awk address.txt. Este código producirá esta salida:

Listado de Código 1.4: La salida del guión

Jimmy the Weasel, 100 Pleasant Drive, San Francisco, CA 12345
Big Tony, 200 Incognito Ave., Suburbia, WA 67890

OFS y ORS

En la instrucción print se puede ver que awk une cadenas de caracteres que son colocadas juntas en una línea. Hemos usado esta característica para insertar una coma y un espacio (", ") entre los tres campos de datos de cada línea. Ahora, si bien este método funciona, no es el más elegante. En lugar de insertar la cadena fija (", ") entre los campos, podremos indicarle a awk que lo haga por nosotros. Ésto lo haremos mediante otra variable, que se llama OFS. Mira esta muestra de código.

Listado de Código 1.5: Código de ejemplo

print "Hola,","Jim!"

Las comas en esta línea no son parte de la cadena literal. En su lugar, ellas le dicen a awk que "Hello", "there", y "Jim!" son campos separados, y que la variable OFS deberá imprimirse entre ambas cadenas. Por defecto, awk produce esto:

Listado de Código 1.6: Salida de awk

Hola, Jim!

Esto nos enseña, que, por defecto, OFS es igual a " ", un solo espacio en blanco. Sin embargo, podemos redefinirlo de forma fácil para que inserte nuestro separador de cambio personalizado. Aquí está una versión actualizada del guión original address.awk que usa ÖFS para añadir estos separadores intermedios (", ").

Listado de Código 1.7: Redefinir OFS

BEGIN {
    FS="\n"
    RS=""
    OFS=", "
}
{ print $1, $2, $3 }

Awk también tiene una variable especial ORS, llamada "separador de campos de salida". Ésta se establece por defecto a ("\n"), con ella podemos controlar qué separador se va a imprimir al final de cada print. Por defecto viene a una sola línea, si lo queremos con doble espaciado, entonces podemos establecer ORS para que sea igual a "\n\n". O, si queremos que los registros estén separados por un simple espacio (sin salto de línea), entonces asignaríamos un espacio en blanco " " a ORS.

De múltiples líneas a tabulado

Imaginemos que lo que queremos es escribir un guión que convierta los registros que ocupan varias líneas en otros que tengan una sola línea cuyos campos vayan separados por tabuladores para importarlos en una hoja de cálculo. Tras usar una versión ligeramente modificada de address.awk, quedaría claro que nuestro programa solo funciona para direcciones compuestas por tres líneas. Si el programa encontrara el siguiente registro, la cuarta línea sería descartada silenciosamente:

Listado de Código 1.8: Ejemplo de registro

Cousin Vinnie
Vinnie's Auto Shop
300 City Alley
Sosueme, OR 76543

Para manejar situaciones como esta, sería bueno que nuestro código pudiera ver el número de campos por registro y tenerlo en cuenta para imprimirlos más tarde en orden. Ahora mismo, el código solo imprime los tres primeros campos de la dirección. El siguiente código hace justo lo que queremos:

Listado de Código 1.9: Código mejorado

BEGIN {
    FS="\n"
    RS=""
    ORS=""
}

{
    x=1
    while ( x<NF ) {
        print $x "\t"
        x++
    }
    print $NF "\n"
}

Primero establecemos el separador de campo FS a "\n" y el separador de registros RS a "" para que awk interprete las direcciones multilínea de forma correcta, como antes. Entonces ponemos la variable separadora del registro de salida ORS a "", lo cual causará que la instrucción print no escriba un carácter de nueva línea tras cada registro. Esto significa que si queremos comenzar una nueva línea, tendremos que imprimirla explícitamente con print "\n".

En el bloque de código principal, creamos una variable x que contiene el número de campo actual. Inicialmente se pone a 1. Entonces usamos un bucle while (idéntico al while del lenguaje C) para iterar a través de todos los campos excepto el último, imprimiendo el campo correspondiente en cada vuelta, y además un carácter de tabulación. Finalmente imprimimos el último campo y un carácter de salto de línea. De nuevo, como ORS está puesto a "", print no imprimirá los caracteres de nueva línea por nosotros. La salida del programa se verá de esta forma, que es exactamente lo que queríamos:

Listado de Código 1.10: La salida deseada. No es bonita, pero al estar delimitada por tabuladores es fácil de importar en una hoja de cálculo

Jimmy the Weasel        100 Pleasant Drive      San Francisco, CA 12345
Big Tony        200 Incognito Ave.      Suburbia, WA 67890
Cousin Vinnie   Vinnie's Auto Shop      300 City Alley  Sosueme, OR 76543

Estructuras de bucle

Ya hemos visto la construcción while de awk, que es idéntica a la de C. Awk también tiene una construcción "do...while", que evalúa la condición al final del bloque de código, en lugar de al principio como se haría en un bloque while estándar. Es similar al "repeat...until" que podemos encontrar en otros lenguajes. Aquí hay un ejemplo:

Listado de Código 1.11: ejemplo de do...while

{
    count=1
    do {
        print "Me imprimo una vez al menos"
    } while ( count != 1 )
}

Como la condición es evaluada tras el bloque, un bucle "do...while", al contrario que un bucle while normal, siempre se ejecuta al menos una vez. Por contra, un bucle normal no se ejecutará jamás si la condición inicial es falsa.

bucles for

Awk permite la creación de bucles for, que, tal y como los while, son idénticos a sus versiones en C:

Listado de Código 1.12: Bucle de ejemplo

for ( initial assignment; comparison; increment ) {
    code block
}

Aquí hay un ejemplo rápido:

Listado de Código 1.13: Ejemplo rápido

for ( x = 1; x <= 4; x++ ) {
    print "vuelta",x
}

Este código imprimirá lo siguiente:

Listado de Código 1.14: Salida del código de arriba

vuelta 1
vuelta 2
vuelta 3
vuelta 4

Break y continue

De nuevo, al igual que en C, awk provee instrucciones break y continue. Dichas sentencias nos brindan un mejor control sobre varios tipos de bucle en awk. Aquí se expone un pedazo de código que necesita desesperadamente una instrucción break:

Listado de Código 1.15: Ejemplo de código que necesita una instrucción break

while (1) {
    print "forever and ever..."
}

Como 1 siempre es cierto, este bucle while duraría para siempre. Aquí hay un bucle que se ejecutaría solo 10 veces:

Listado de Código 1.16: Bucle que se ejecuta 10 veces

x=1
while(1) {
    print "vuelta",x
    if ( x == 10 ) {
        break
    }
    x++
}

Aquí, el break se usa para salir del bucle más interior. Se sale del bucle y la ejecución continúa en la línea justo después del bloque del bucle.

La instrucción continue complementa a break, y funciona de esta forma:

Listado de Código 1.17: La instrucción continue complementando a break

x=1
while (1) {
    if ( x == 4 ) {
        x++
        continue
    }
    print "vuelta",x
    if ( x > 20 ) {
        break
    }
    x++
}

Este código imprimirá desde "iteration 1" hasta "iteration 21", exceptuando a "iteration 4". En la vuelta número 4, x es incrementado y la instrucción continue es ejecutada, lo cual provoca el salto a la siguiente iteración sin ejecutar ni una línea más de la vuelta actual. La instrucción continue funciona para todos los tipos de bucle interactivo en awk, al igual que break. Cuando se usa en el cuerpo de un bucle for, continue provoca el incremento de la variable de control. Veamos el equivalente en un bucle for:

Listado de Código 1.18: Bucle for equivalente

for ( x=1; x<=21; x++ ) {
    if ( x == 4 ) {
        continue
    }
    print "vuelta",x
}

No es necesario incrementar x tras el continue, porque el bucle for, al contrario que el while, la incrementa de forma automática.

Matrices

Te gustará saber que awk es capaz de manejar matrices. Sin embargo, aquí comienzan en 1 en lugar de 0:

Listado de Código 1.19: Ejemplo de matriz en awk

myarray[1]="jim"
myarray[2]=456

Cuando awk encuentra la primera asignación de myarray, la matriz es creada, y myarray[1] pasa a contener "jim". Al pasar a la siguiente asignación, la matriz pasa a contener dos elementos diferenciados. has two elements.

Una vez definida la matriz, awk tiene mecanismos para iterar sobre sus elementos de la forma siguiente:

Listado de Código 1.20: Iterando sobre matrices

for ( x in myarray ) {
    print myarray[x]
}

Este código imprimirá todos los elementos dentro de myarray. Al usar esta forma especial "in" en un bucle for, awk asignará todos y cada uno de los índices existentes en myarray a x (la variable de control del bucle) en sucesivos turnos, ejecutando el código del bucle una vez tras cada reasignación de x. Si bien se trata de una peculiaridad bastante cómoda en awk, también es cierto que tiene una parte negativa -- cuando awk cicla entre los índices de la matriz, no sigue ningún orden particular. Esto quiere decir que no tenemos forma de saber si la salida del código de arriba será:

Listado de Código 1.21: Salida del código de arriba

jim
456

o

Listado de Código 1.22: Otra salida para el código de arriba

456
jim

Parafraseando libremente a Forrest Gump, iterar sobre los elementos de una matriz en awk es como abrir una caja de bombones -- nunca sabes lo que te va a tocar. Todo esto se debe al carácter encadenado de las matrices en awk, aspecto que veremos ahora.

Naturaleza de cadena de las matrices

En mi artículo anterior, ya comenté que awk almacena las variables numéricas como cadenas de caracteres. Si bien awk realiza las conversiones para que todo funcione, también es verdad que abre las puertas a la escritura de código de aspecto extraño:

Listado de Código 1.23: Código de aspecto extraño

a="1"
b="2"
c=a+b+3

Tras de que este código se ejecute, c es igual a 6 ya que awk suma las cadenas "1" y "2" tal y como si fueran los números 1 y 2. En ambos casos, awk solucionará el problema correctamente. Esta naturaleza de awk es bastante intrigante -- te preguntarás que es lo que pasará si usamos índices de cadena para las matrices, por ejemplo, en este código:

Listado de Código 1.24: Código de ejemplo

myarr["1"]="Mr. Whipple"
print myarr["1"]

Tal y como esperarías, este código imprime "Mr. Whipple". Pero ¿y si omitimos las comillas alrededor del segundo 1?

Listado de Código 1.25: Quitando las comillas en el print

myarr["1"]="Mr. Whipple"
print myarr[1]

Adivinar el resultado de esto es más complicado. ¿Considerará awk que myarr["1"] y myarr[1] son dos elementos distintos de la matriz? ¿o quizás los considere como referencias al mismo elemento? La respuesta correcta es que awk los considerará como el mismo elemento, y por tanto, imprimirá "Mr. Whipple", tal y como hizo en el otro ejemplo. Aunque parezca extraño, awk ha estado usando internamente índices de cadena todo el rato.

Y tras conocer este comportamiento, alguien estará tentado de ejecutar algo como esto:

Listado de Código 1.26: Código extraño

myarr["name"]="Mr. Whipple"
print myarr["name"]

Y no solo no provoca un error, sino que además funciona de forma idéntica a los ejemplos anteriores, e imprimirá también el mismo resultado, "Mr. Whipple". Como ves, awk no nos limita al uso de índices enteros, podemos usar cadenas sin problema alguno. Cuando usamos una cadena a modo de índice como en myarr["name"], estamos usando matrices asociativas. Técnicamente awk no está haciendo nada distinto en la trastienda ya que incluso cuando usamos un entero, awk lo trata como si éste fuera una cadena. Sin embargo, aún deberíamos llamarlas matrices asociativas -- suena bien e impresionará a tu jefe. El índice numérico que en realidad es una cadena será nuestro pequeño secreto. ;)

Utilidades de matrices

Cuando nos referimos a matrices, awk nos ofrece bastante versatilidad. Podemos usar índices de cadena y no estamos obligados a tener secuencias de índices numéricos continuos (por ejemplo, podemos definir myarr[1] y myarr[1000], y dejar los demás sin definir). Si bien todo esto puede ser útil, en algunas circunstancias puede crear confusión. Afortunadamente, awk ofrece un par de ventajas que hacen que las matrices sean algo más manejables.

Podemos borrar elementos de una matriz, por ejemplo, para borrar un elemento haríamos:

Listado de Código 1.27: Borrando elementos de la matriz

delete fooarray[1]

Si quieres ver si un elemento particular existe, puedes usar el operador lógico especial "in" tal y como sigue:

Listado de Código 1.28: Comprobar si un elemento existe

if ( 1 in fooarray ) {
    print "Si!  Aquí está."
} else {
    print "No!  No se pudo encontrar."
}

La próxima vez

Hemos cubierto mucha materia en este artículo. La próxima vez, redondeare un poco el conocimiento sobre awk enseñándote como usar funciones matemáticas y de cadenas en awk, y verás como crear tus propias funciones. También te guiaré sobre la creación de un programa de gestión contable. Mientras tanto, te recomiendo que escribas tus propios programas en awk, y también los siguientes recursos en la web:

2.  Recursos



Imprimir

Página actualizada 31 de octubre, 2005

Sumario: En esta secuela de la introducción a awk, Daniel Robbins continúa explorando awk, un gran lenguaje de extraño nombre. Daniel nos enseñará como manejar registros compuestos por múltiples líneas, usar construcciones de bucle y crear y usar matrices en awk. Al final de este artículo estarás versado en una amplia variedad de funcionalidades, y estarás preparado para escribir tus propios guiones en awk.

Daniel Robbins
Autor

Jesús Guerrero
Traductor

Donate to support our development efforts.

Copyright 2001-2014 Gentoo Foundation, Inc. Questions, Comments? Contact us.