10. Arrays en awk

Un array es una tabla de varios valores, llamados elementos. Los elementos de un array se distinguen por sus índices. Los índices pueden ser tanto cadenas como números. Cada array tiene un nombre, que es similar al nombre de una variable, pero no debe estar siendo usado como nombre de variable en un mismo programa awk.

Introducción a los Arrays

El lenguaje awk tiene arrays de una dimensión para almacenar grupos de cadenas o números relacionados.

Todos los arrays en awk deben tener un nombre. Los nombres de los arrays presentan la misma sintáxis que los nombres de variables; cualquier nombre de variable válido será un nombre válido para un array. Pero no se puede usar un mismo nombre para un array y una variable en un mismo programa awk.

Los arrays en awk se asemejan superficialmente a los arrays en otros lenguajes de programación; pero existen diferencias fundamentales. En awk, no es necesario especificar el tamaño de un array antes de empezar a usarlo. Lo que és más, en awk, cualquier número o incluso cadena podría ser usado como un índice de array.

En la mayoría de otros lenguajes, tienes que declarar un array y especificar cuantos elementos o componentes tiene. En dichos lenguajes, la declaración causa la reserva de un bloque de memoria contiguo para tantos elementos como se hayan especificado. Un índice para esos arrays debe ser un entero positivo, por ejemplo, el índice 0 especifica el primer elemento del array, el cual se encuentra realmente almacenado al principio del bloque de memoria reservado. Índice 1 especifica el segundo elemento, el cual está almacenado en la memoria justo a continuación del primer elemento, y así sucesivamente. No es posible añadir más elementos al array, porque la reserva de memoria ya se ha realizado para el número de elementos que se declaró inicialmente que iba a tener el array.

Un array contiguo de cuatro elementos podría presentar una forma similar a la siguiente, conceptualmente, si los valores de los elementos son 8, “foo”,“” y 30:

+---------+---------+--------+---------+

¦    8    ¦  "foo"  ¦   ""   ¦    30   ¦    value

+---------+---------+--------+---------+

     0         1         2         3        index

Solamente los valores son almacenados; los índices son implícitos por el orden de los valores. 8 es el valor del índice 0, porque 8 aparece en la posición con 0 elementos antes de él.

Los arrays en awk son diferentes: son asociativos. Esto significa que cada array es una colección de pares: un índice, y su valor de elemento de array correspondiente:

Elemento 4     Valor 30

Elemento 2     Valor "foo"

Elemento 1     Valor 8

Elemento 3     Valor ""

Hemos mostrado los pares en un orden aleatorio porque el orden no tiene ningún significado.

Una ventaja de un array asociativo es que puedes añadir nuevas parejas en cualquier momento. Por ejemplo, supón que añadimos a ese array un elemento décimo cuyo valor es “número diez”. El resultado es este:

Elemento 10    Valor "número diez"

Elemento 4     Valor 30

Elemento 2     Valor "foo"

Elemento 1     Valor 8

Elemento 3     Valor ""

Ahora el array es disperso (algunos índices no aparecen): tiene los elementos 4 y 10, pero no tiene los elementos 5, 6, 7, 8, y 9.

Otra consecuencia de los arrays asociativos es que los índices no tienen por qué ser enteros positivos. Cualquier número, o incluso una cadena, puede ser un índice de un array. Por ejemplo, aquí está un array el cual traduce palabras de Inglés a Francés:

Elemento "dog" Valor "chien"

Elemento "cat" Valor "chat"

Elemento "one" Valor "un"

Elemento 1     Valor "un"

Aquí nosotros decidimos traducir el número 1 en ambas formas, la forma numérica y alfabética, lo que supone un ejemplo claro de que un array puede tener ambos números y cadenas como índices.

Cuando awk crea un array para ti, por ejemplo con la función implícita split (Ver la sección Funciones Implícitas (Built-in) para Manipulación de Cadenas), esos índices del array son enteros consecutivos que comienzan con el 1.

Refiriéndose a un elemento de un Array

La forma principal de usar un array es referirse a uno de sus elementos. Una referencia a un array es una expresión que presenta la siguiente forma:

array[índice]

Aquí array es el nombre de un array. La expresión índice es el índice del elemento del array que tú quieres.

El valor de la referencia al array es el valor actual del elemento del array. Por ejemplo, foo[4,3] es una expresión para el elemento cuyo índice es 4,3 del array foo.

Si referencias un elemento de array en el que no se ha grabado ningún valor, el valor devuelto por esta referencia es “ ”, la cadena nula. Esto incluye elementos a los cuales no les has asignado un valor, y los elementos que han sido borrados (Ver la sección La Sentencia delete). Esta referencia automáticamente crea  dicho elemento de array, con la cadena nula como su valor. (en algunos casos esto es un incoveniente ya que supone un desperdicio de memoria por parte de awk).

Puedes averigurar si existe un elemento en un array para un determinado índice con la expresión:

índice in array

Esta expresión chequea si existe o no el índice especificado, sin el efecto lateral de crear dicho elemento si no está presente. La expresión tendrá el valor de 1 (verdadero) si array[índice] existe, y 0 (falso) si no existe.

Por ejemplo, para chequear si el array frequencies contiene el índice “2”, podrías escribir esta sentencia:

if ("2" in frequencies) print "Subscript \"2\" is present."

Señalar que esto no es un chequeo de si el array frequencies contiene o no un elemento cuyo valor es “2”. (No hay forma de hacer esto, excepto escaneando todos los elementos). También, esta sentencia no crea frequencies[“2”], mientras que la siguiente sentencia (incorrecta) alternativa si lo haría:

if (frequencies["2"] != "") print "Subscript \"2\" is present."

Asignación de Elementos de Array

Los elementos de un array son valoresi: les pueden ser asignados valores del mismo modo que a las variables awk.

array[subíndice] = valor

Aquí array es el nombre de tu array. La expresión subíndice es el índice del elemento del array al que le quieres asignar un valor. La expresión valor es el valor que le estás asignando al elemento del array.

Un ejemplo básico de un Array

El siguiente programa toma una lista de líneas, cada una comenzando con un número de línea, y las imprime en orden del número de línea. Los números de línea no están en orden. Este programa ordena las líneas creando un array usando los números como subíndices. Después imprime las líneas ordenadas por sus números. Es un programa muy simple, y se confunde si encuenta números repetidos, huecos o líneas que no comiencen con un número.

{

  if ($1 > max)

    max = $1

  arr[$1] = $0

}

END {

  for (x = 1; x <= max; x++)

    print arr[x]

}

La primera regla guarda el número de línea más alto leído hasta el momento; también guarda cada línea en el array arr, en un índice que es el número de línea.

La segunda regla se ejecuta después de que se hayan leído toda la entrada, para imprimir todas las líneas. Cuando este programa se ejecuta con la siguiente entrada:

5  I am the Five man

2  Who are you?  The new number two!

4  . . . And four on the floor

1  Who is number one?

3  I three you.

su salida es la siguiente:

1  Who is number one?

2  Who are you?  The new number two!

3  I three you.

4  . . . And four on the floor

5  I am the Five man

Si se repite un número de línea, la última línea con dicho número repetido es la que permanece.

Los huecos en números de línea pueden ser manejados con una fácil mejora a la regla END del programa:

END {

  for (x = 1; x <= max; x++)

    if (x in arr)

      print arr[x]

}

Recorrido de todos los elementos de un Array

En programas que utilizan arrays, a menudo necesitas un bucle que se ejecute para cada elemento de un array. En otros lenguajes, donde los arrays son contíguos y los índices están limitados a enteros positivos, esto es fácil: el índice mayor es una unidad menor que el tamaño del array, y puedes encontrar todos los índices válidos recorriendo desde 0 hasta dicho valor. Esta técnica no servirá en awk, ya que cualquier número o cadena podría ser un índice de array. Así que awk tiene un tipo especial de sentencia for para el recorrido de arrays:

for (variable in array)

  cuerpo

Esto hace que se ejecute cuerpo una vez para cada valor diferente que su programa haya usado previamente como un índice en array, con la variable variable que recibe el valor de dicho índice.

Aquí tienes un programa que utiliza esta forma de la sentencia for. La primera regla escanea los registros de entrada y anota que palabras aparecen (al menos una vez) en la entrada, almacenando un 1 en el array, usando la palabra en cuestión como índice. La segunda regla escanea los elementos del array used para encontrar todas las palabras distintas que aparecen en la entrada. Imprime todas las palabras que son de más de 10 caracteres y también imprime el número de veces que aparecen dichas palabras en el fichero de entrada. Ver la sección 11. Funciones Implícitas (Built-in) , para más información de la función implícita length.

# Record a 1 for each word that is used at least once.

{

  for (i = 1; i <= NF; i++)

    used[$i] = 1

}

# Find number of distinct words more than 10 characters long.

END {

  num_long_words = 0

  for (x in used)

    if (length(x) > 10) {

      ++num_long_words

      print x

  }

  print num_long_words, "words longer than 10 characters"

}

Ver la sección 16. Programa Ejemplo, para ver un ejemplo más detallado de este tipo.

El orden en el cual esta sentencia accede a los elementos del array es determinado por la disposición interna de los elementos del array dentro de awk y no puede ser controlada ni cambiada. Esto puede llevar a problemas si se añaden nuevos elementos al array mediante sentencias en cuerpo, no se puede predecir si el bucle for recorrerá o no estos nuevos elementos. De forma similar, cambiando variable dentro del bucle podría producir resultados extraños.

La Sentencia delete

Puedes eliminar un elemento individual de un array utilizando la sentencia delete:

delete array[index]

Cuando un elemento de array es eliminado, es como sí nunca lo hubieses referenciado y nunca le hubieses dado un valor. Cualquier valor que tuviese el elemento del array eliminado nunca podrá ser obtenido.

Aquí está un ejemplo de la eliminación de elementos en un array:

for (i in frequencies)

  delete frequencies[i]

El ejemplo elimina todos los elementos del array frequencies.

Si eliminas un elemento, una sentencia for realizada a continuación para escanear el array no te devolverá dicho elemento y el operador in para chequear la existencia de un elemento te devolverá un 0:

delete foo[4]

if (4 in foo)

  print "Esto nunca será impreso"

Arrays Multi-Dimensionales

Un array multidimensional es un array en el cual un elemento es identificado por una secuencia de índices, no por un único índice. Por ejemplo, un array bidimensional requiere dos índices. La forma normal (en la mayoría de los lenguajes incluyendo awk) para referirse a un elemento de un array bidimensional llamado grid es mediante grid[x,y].

Los arrays multidimensionales son soportados en awk mediante la concatenación de índices en una cadena. Lo que ocurre es que awk convierte los índices en cadenas (Ver la sección Conversiones de Cadenas y Números ) y los concatena juntos, con un separador entre ellos. Esto crea una única cadena que describe el valor de los índices separados. La cadena combinada es usada como un índice único (normal) en un array unidimensional normal. El separador usado es el valor de la variable implícita SUBSEP.

Por ejemplo, supón que evaluamos la expresión foo[5,12]=”valor” donde el valor de SUBSEP es “@”. Los números 5 y 12 están concatenados con una coma entre ellos, produciendo “5@12”; por lo que, el elemento del array foo[“5@12”] es fijado a “valor”.

Una vez que el valor del elemento es almacenado, awk no tiene forma de saber si se almacenó como un índice único o como una secuencia de índices. Las dos expresiones foo[5,12] y foo[5 SUBSEP 12] siempre tienen el mismo valor. El valor por defecto de SUBSEP es actualmente la cadena “\034”, que contiene un carácter no imprimible que es poco probable que aparezca en un programa awk o en los datos de entrada.

La falta de utilidad de elegir un carácter poco probable viene del hecho de que los valores de índices que contienen una cadena que concuerde con SUBSEP llevan a cadenas combinadas que son ambiguas. Supón que SUBSEP fuese “@”; entonces foo[“a@b”, “c”] y foo[“a”, “b@c”] serían indistinguibles porque ambas serían realmente almacenadas como foo[“a@b@c”]. Debido a que SUBSEP es “\034”, tales confusiones pueden ocurrir realmente solo cuando un índice contiene el carácter con código ASCII 034, lo cual es muy raro.

Puedes chequear si una secuencia de índice en particular existe en un array “multidimensional” con el mismo operador in utilizado para arrays de una sola dimensión. En lugar de un índice simple como el operando izquierdo, escribe la secuencia completa de índices separados por comas, en paréntesis:

(subscript1, subscript2, ...) in array

El siguiente ejemplo trata su entrada como un array bidimensional de campos; rota este array 90 grados en el sentido de las agujas del reloj e imprime el resultado. Asume que todas las líneas tienen el mismo número de elementos.

awk '{

     if (max_nf < NF)

          max_nf = NF

     max_nr = NR

     for (x = 1; x <= NF; x++)

          vector[x, NR] = $x

}

END {

     for (x = 1; x <= max_nf; x++) {

          for (y = max_nr; y >= 1; --y)

               printf("%s ", vector[x, y])

          printf("\n")

     }

}'

Cuando se le pasa la siguiente entrada:

1 2 3 4 5 6

2 3 4 5 6 1

3 4 5 6 1 2

4 5 6 1 2 3

produce la siguiente salida:

4 3 2 1

5 4 3 2

6 5 4 3

1 6 5 4

2 1 6 5

3 2 1 6

Recorrido de Arrays Multi-Dimensionales

No existe ninguna sentencia especial for que escanee un array multidimensional; no puede existir ninguna, porque en realidad no hay arrays o elementos multidimensionales; existe solo una forma de acceso a un array multidimensional.

Sin embargo, si tu programa tiene un array que es siempre accedido como multidimensional, puedes obtener el efecto de escanearlo combinando la sentencia de escaneo for (Ver la sección Recorrido de todos los elementos de un Array ) con la función implícita split (Ver la sección Funciones Implícitas (Built-in) para Manipulación de Cadenas). Funciona de la siguiente manera:

for (indice_combinado in array) {

  split(índice_combinado, array_índices, SUBSEP)

  ...

}

Esto encuentra cada concatenación, índice combinado del array, y lo divide en índices individuales separándolos por las posiciones donde aparezca el valor de SUBSEP. Los índices separados se convierten en los elementos del array array_índices.

Por lo que, supón que has almacenado previamente en array[1,”foo”]; entonces un elemento con índice "1\034foo" existe en el array. (Recuerda que el valor por defecto de SUBSEP contiene el carácter con código 034). Antes o después, la sentencia for encontrará ese índice y realizará una iteración en la cual índice_combinado tomará el valor "1\034foo". Entonces la función split se llamará con los siguientes parámetros:

split("1\034foo", array_índices, "\034")

El resultado de esto es fijar el valor 1 para array_índices[1] y el valor “foo” para array_índices[2]. Por lo que, la secuencia original de índices separados ha sido recuperada.

   
Índice
Manual