SGCG

…esto no es un subtítulo…

Ir a: contenido categorías calendario archivo suscripción

Volver arriba

Un pequeño filtro

2009-10-03

Seguimos con *nix en casos prácticos. Esta vez vamos a preparar un pequeño programita para filtrar el contenido de una larga lista. El caso está basado en uno real en el que un compañero tuvo que filtrar una larguísima lista de ficheros con resultados de un experimento. Se trata del típico problema con el que se encuentra alguien que tiene que manejar una gran cantidad de datos y seleccionar una parte.

El problema

Tenemos un fichero de texto simple que contiene muchísimas líneas. Cada línea es un elemento de una lista. Cada línea o elemento de la lista tiene dos palabras separadas por un guión: un nombre y un número. Puede haber muchas líneas con el mismo nombre, pero el número no se repite. La lista tendría que estar ordenada con el segundo campo, el número, pero nos ha llegado desordenada. Un fragmento típico de la lista tiene el siguiente aspecto:
QUUX-000064 WOMBAT-000053 FOOBAR-000022

Hay que filtrar la lista. Hay varios criterios que dependen del nombre que tenga cada línea: para algunos nombres, tenemos que sacar uno de cada varios (si es uno de cada uno, todos); para otros nombres, hay que sacar los primeros elementos (los cuatro primeros, los cinco primeros…); finalmente, para otros nombres, hay que sacar los últimos elementos (los dos últimos, los tres últimos…).

Diseño de la solución

Sería conveniente definir las instrucciones, las reglas sobre qué hay que filtrar para cada nombre, en un pequeño ficherito. Así podremos separar la lógica general (cómo se filtra) de la particular (qué se filtra). La sintaxis será sencilla; el fichero con las instrucciones tendrá varias líneas, cada una con el siguiente contenido:
nombre regla parámetro
El nombre es el nombre a buscar; la regla será una entre every (para la regla de uno de cada varios), first (para coger los primeros elementos) y last (para coger los últimos elementos); y el parámetro será la frecuencia con la que sacaremos elementos de la lista para la regla every, el número de primeros elementos para la regla first y el número de últimos elementos para la regla last. Por ejemplo, para sacar un elemento de cada cuatro para el nombre FOOBAR, los tres primeros elementos de nombre WOMBAT y los cinco últimos elementos de nombre QUUX, el fichero de reglas tendría el siguiente aspecto:
FOOBAR every 4 WOMBAT first 3 QUUX last 5

Definido esto, ahora tenemos que crear nuestro filtro. De todas las formas que hay de hacerlo, vamos a usar una que es muy poco eficiente desde el punto de vista computacional, pero que nos llevará tan poco tiempo que merecerá la pena: leeremos el fichero con las reglas línea por línea y filtraremos la lista completa cada vez. Podemos hacer esto muy rápidamente con las herramientas de texto de *nix. Como vamos a filtrar muy pocas listas y el ordenador no tarda mucho en terminar su tarea, una solución más sofisticada y eficiente desde el punto de vista computacional pero más lenta de elaborar está en clara desventaja.

Leeremos el fichero de reglas. Para cada línea, extraeremos el nombre, la regla y el argumento numérico. Después, dependiendo de la regla que tengamos, aplicaremos un filtro u otro. Para filtrar, lo primero que haremos será ordenar la lista y después aplicaremos los filtros correspondiente. El programa admitirá dos argumentos: el nombre del fichero con las reglas y el nombre del fichero con la lista. Sacará sus resultados por su salida estándar.

Implementación de la solución

El programa será un script de la shell. Leeremos el fichero con las reglas línea por línea en un bucle. De cada línea extraeremos los campos (nombre, regla y argumento numérico) con una herramienta sencilla para extraer campos (cut). Después, ordenaremos la lista con sort y filtraremos el resultado con grep para sacar las líneas con el nombre que estamos buscando. La salida de grep irá dirigida al filtro apropiado para cada regla. Los filtros first y last son fáciles de aplicar con las herramientas head y tail, respectivamente. El filtro every es un poquito más complicado, pero podemos hacerlo cómodamente con awk. El resultado final puede tener un aspecto como el siguiente:

#!/bin/sh # En primer lugar, obtenemos los nombres de los ficheros de # los argumentos. RULES_FILE="$1" LIST_FILE="$2" # Leemos el fichero de reglas línea por línea. cat "$RULES_FILE" | while read line; do # Extraemos los campos de la línea. name=`echo $line | cut -d' ' -f1` rule=`echo $line | cut -d' ' -f2` argument=`echo $line | cut -d' ' -f3` # Ordenamos numéricamente según el campo 2 # usando el guión como separador. La # salida ordenada pasa por grep, que busca # las líneas que empiezan por el nombre # especificado en la regla. La salida # de grep va a los filtros finales. sort -n -k2 -t- "$LIST_FILE" | grep "^$name" | case $rule in # Para sacar las primeras incidencias. first) head -n $argument ;; # Para sacar las últimas incidencias. last) tail -n $argument ;; # Para sacar una incidencia de cada # tantas. every) awk 'BEGIN { N = '$argument' } (NR % N) == 1 { print }' ;; esac done

¡Ya está! Si ignoramos los comentarios y las líneas en blanco, podemos decir que hemos creado el filtro en 17 líneas. Si hemos llamado al script filterit, al fichero de reglas rules y al fichero con la lista list, podemos hacer lo siguiente para tener el resultado en un fichero, results:
./filterit rules list > results

Para hacer pruebas, aquí hay ficherillos útiles:


Categorías: Informática

Permalink: http://sgcg.es/articulos/2009/10/03/un-pequeno-filtro/