16. Programa Ejemplo

El siguiente ejemplo es un programa awk completo, el cual imprime el número de ocurrencias de cada palabra en su entrada. Ilustra la naturaleza asociativa de los arrays de awk usando cadenas como subíndices. También demuestra la construcción `for x in array'. Finalmente, muestra como se puede usar awk en conjunción con otros programas de utilidades para realizar una tarea útil de relativa complejidad con un esfuerzo mínimo. Algunas explicaciones aparecen a continuación del listado del programa.

awk '

# Imprime lista de frecuencias de palabras

{

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

        freq[$i]++

}

END {

    for (word in freq)

        printf "%s\t%d\n", word, freq[word]

}'

La primera cosa a resaltar de este programa es que tiene dos reglas. La primera regla, debido a que tiene un patrón vacío, se ejecuta para cada línea de la entrada. Utiliza el mecanismo de acceso a los campos de awk (Ver la sección Examinando campos) para extraer las palabras individuales de la línea, y la variable implícita NF (Ver la sección 13. Variables Implícitas (Built-in)) para conocer cuantos campos están disponibles.

Para cada palabra de entrada, un elementos del array freq se incrementa para reflejar que dicha palabra ha aparecido otra vez en el fichero de entrada.

La segunda regla, debido a que tiene el patrón END, no se ejecuta hasta que se ha acabado de leer la entrada. Imprime el contenido del array freq que ha sido construido dentro de la primera acción.

Advierta que este programa presenta distintos problemas al aplicarlos sobre ficheros de texto reales, ya que no tiene en cuenta los signos de puntuación.

El lenguaje awk considera los caracteres en mayúsculas y minúsculas como distintos. Por lo tanto, ‘foo’ y ‘Foo’ no son tratados por este programa como la misma palabra. Esto es indeseable ya que en el texto normal, las palabras comienzan por mayúsculas si son comienzo de sentencias, y un analizador de frecuencias no debería ser sensible a eso.

La salida no aparece en un orden útil. Podrías estar interesado en saber qué palabras aparecen más frecuentemente, o sacar las palabras y sus respectivas frecuencias en orden alfabético.

La forma de resolver estos problemas es usar otras utilidades de sistema para procesar la entrada y salida del script de awk. Supón que el script mostrado más arriba se salva en el fichero ‘frequency.awk’. Entonces el comando de shell:

tr A-Z a-z < file1 ¦ tr -cd 'a-z\012' \

  ¦ awk -f frequency.awk \

  ¦ sort +1 -nr

produce un tabla de las palabras que aparecen en el fichero ‘file1’ en orden de frecuencia de aparición decreciente.

El primer comando tr en este pipeline transforma todas las letras en mayúsculas del fichero ‘fichero1’ a minúsculas. El segundo comando tr elimina todos los caracteres de la entrada excepto los caracteres en minúsculas y los caracteres newline. El segundo argumento del segundo tr esta entre comillas para proteger a la barra invertida de que sea interpretada por la shell. El programa awk lee los datos de entradas con las transformaciones realizadas previamente por la ejecución de los comandos tr y produce una tabla de frecuencia de palabras, la cual no está ordenada.

La salida del script awk es ahora ordenada por el comando sort e impresa en el terminal. Las opciones suministradas a sort en este ejemplo especifican la ordenación por el segundo campo de cada línea de entrada (saltándose un campo), que las claves de ordenación sean tratadas como cantidades numéricas (de otra forma ‘15’ vendría antes de ‘5’), y que la ordenación se debería hacer en orden descendente (inverso).

Caso Práctico 1

Supón que, en un sistema DataWarehouse, tienes que tratar un fichero de texto gigante (de varios cientos de Megabytes) y te gustaría conocer los valores que te envían para varios de sus campos que hacen referencia a la clave primaria de otras tablas.

El siguiente ejemplo es un programa de Shell Script (Korn Shell de Unix) en el que se utiliza awk para realizar lo que sería una agrupacion en SQL, para saber los valores que presenta un determinado campo, el número de registros que tiene cada valor y su porcentaje con respecto al total de registros.

Las consideraciones para este ejemplo son las siguientes:

  • Se va a tratar un fichero de texto plano en el cual cada línea representa un registro. Este fichero plano se llamara "tarjetas.txt"
  • Los distintos campos de cada registro del fichero estarán separados por el carácter "~" (alt+126). Destrás del último campo no irá el carácter "~"
  • Cada línea del fichero presentará los siguientes campos: código de la tarjeta, matrícula, fecha de alta de la tarjeta, fecha de baja de la tarjeta, codigo de bloqueo, marca de la tarjeta, cuenta del cliente
  • Se trata de hacer un estudio de los distintos códigos de bloqueo y las distintas marcas de tarjeta existentes.
  • Una muestra de los registros que presenta este fichero sería la siguiente:

    7083211086652975~SIN MATRIC~1999-07-15~9999-12-31~z~008~1086658
    704314580149969~M-9870-ID~1998-04-06~9999-12-31~z~005~145806
    7078831752339974~M-4921-OV~1997-06-26~1999-04-26~z~001~175239
    7078830306279991~M-4730-OH~1994-02-08~1996-03-12~z~001~30633
    7078830977159967~553745C~1996-07-03~1998-08-05~z~001~97721

    Aquí tienes el programa:

    #!/bin/ksh

    # #########################################################################

    # El formato del fichero o paquete a procesar sera el siguiente:

    # campo1~campo2~campo3~campo4~campo5

    # !!! DETRAS DEL ULTIMO CAMPO NO IRA EL SEPARADOR !!!

    #

    # Para quitarle los gusanillos del final de linea si los tuviera habria que

    # ejecutar lo siguiente:

    #

    # sed "1,$ s/\~$//g" fichero_a_tratar > fichero_tratado

    # #########################################################################

    clear

    nawk -F~ ' {

    num_total_reg++;

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

    # Posibilidad 1: Agrupar y obtener los distintos valores para

    # todos y cada uno de los campos

    # v_array[i,$i]++;

    # Posibilidad 2 (la del ejemplo): Que solo nos saque la agrupacion de unos

    # determinados campos. Codigo de bloqueo de la tarjeta

    # (campo 5) y marca de la tarjeta (campo 6)

    if (i==5 ¦¦ i==6) v_array[i,$i]++;

    }

    }

    END {

    for (i in v_array) {

    split(i,array_sep,SUBSEP);

    print array_sep[1],"~",array_sep[2],"~",v_array[i],"~",(v_array[i]/num_total_reg)*100,"~";

    }

    } ' tarjetas.txt > /tmp/kk.txt

    sort -t~ +0n +2nr /tmp/kk.txt > /tmp/kk2.txt

    if [ $? -ne 0 ]

    then

    echo "Error al ordenar el fichero /tmp/kk.txt"

    exit 9

    fi

    rm /tmp/kk.txt

    nawk -F~ '

    BEGIN {

    num_campo = 0;

    num_reg_total = 0;

    valor_anterior = "??????";

    print " Nombre Campo Valor Num. Regs % ";

    print "------------------ ------------------------------ ---------- ---------";

    }

    {

    # Vamos a controlar cuando se cambia de campo para hacer un salto de linea

    if (valor_anterior!=$1) {

    num_campo++;

    valor_anterior=$1;

    print "";

    }

    # Calculo del numero de registro totales del fichero tratado

    if (num_campo==1) { num_reg_total += $3; }

    # Asociamos el numero de campo con su DESCRIPCION

    if ($1==5) $1="cod_bloqueo_trj";

    if ($1==6) $1="marca_tarjeta";

    # Formateamos el porcentaje para alinearlo por la derecha

    porcentaje = sprintf("%3.4f%%",$4);

    printf "%18s %30s %10s %9s\n",$1,$2,$3,porcentaje;

    }

    END {

    print "";

    print " Numero total de registros procesados: ",num_reg_total;

    } ' /tmp/kk2.txt > /tmp/out.txt

    cat /tmp/out.txt

    rm /tmp/kk2.txt

    rm /tmp/out.txt

    La salida que generaría la ejecución de este Shell Script es la siguiente:

    Nombre Campo Valor Num. Regs %

    ------------------ ------------------------------ ---------- ---------

    cod_bloqueo_trj z820996.2481%

    cod_bloqueo_trj411381.6180%

    cod_bloqueo_trj35760.8911%

    cod_bloqueo_trj17510.5980%

    cod_bloqueo_trj43310.3635%

    cod_bloqueo_trj37240.2814%


    marca_tarjeta008592469.4571%

    marca_tarjeta001211524.7977%

    marca_tarjeta0072152.5208%

    marca_tarjeta0021501.7587%

    marca_tarjeta0051161.3601%

    marca_tarjeta00490.1055%


    Numero total de registros procesados: 8529

       
    Índice
    Manual