lunes, 2 de febrero de 2009

HAL y los trabalenguas (I)

Recordemos someramente por dónde nos quedamos en nuestros primeros pasos hacia el inframundo.

Nuestro problema era guardar en un fichero la lista, formateada adecuadamente, de todas las referencias cruzadas que aparecen en el artículo relativo al término hacker en el Jargon File.

De las cuatro tareas en que subdividimos el problema contábamos ya con la solución de dos y el día pasado esbozamos un modelo de solución para una tercera.

Al final de aquel primer día de andadura llegamos a plantear, incluso, un esquema de la solución total mediante tuberías y redirección:

dict -D jargon hacker | ???? | awk '{print "hacker:"; print " " $1}' >jargon_ref

Las interrogaciones ocupan el lugar de la orden con la que hoy debemos enfrentarnos y que nos pondrá ante las fauces del monstruo devorador de ojos, las expresiones regulares.

Dirijámonos a él sin mayor dilación.

El objetivo de la orden aún no encontrada es extraer de la definición que el Jargon File da del término hacker todas las referencias cruzadas a otros artículos de ese mismo diccionario. Puesto que estas referencias están señaladas mediante llaves, el objetivo es equivalente a obtener todas las expresiones del tipo {referencia_cruzada} que hay en la definición aludida, donde 'referencia_cruzada' puede ser cualquier palabra o grupo de palabras.

Atiéndase a este último aspecto. Cuáles sean las referencias cruzadas en concrreto es algo para nosotros a priori totalmente desconocido, salvo por tres cosas:

  • Está entre llaves.

  • Consta de caracteres alfabéticos o, si se quiere, letras; quizá también combinadas con números.

  • Puede contener más de una palabra. Lo que significa que, además de los caracteres alfabéticos y numéricos, puede haber espacios.


Ahora bien, puesto que para poder extraer un elemento de un todo necesitamos primero dar con ese elemento, esto es, localizarlo dentro de ese todo, la cuestión queda reducida a lo que parece una imposibilidad: si no sabemos exactamente qué es lo que buscamos, ¿cómo podemos encontrarlo? Dicho de otra forma: es fácil buscar una palabra concreta dentro de un texto ---es algo que HAL ha hecho ya por nosotros con ayuda de grep---, pero parece aventurado buscar un trozo de texto del que sólo conocemos dos o tres características muy vagas.

Lo que parece imposible no lo es tanto. Si nos fijamos, es algo que hacemos continuamente y que hemos hecho desde nuestra más tierna infancia. Pensemos, sin ir más lejos, en un niño de dos años que provisto de un molde se afana por ver qué objetos de los muchos que tiene a su alrededor en el barullo de sus juguetes encaja con ese molde. Es cierto que para un adulto ésta es una diversión terriblemente aburrida, pero sin duda factible y muy entretenida para el infante.

No podemos pensar que una mente tan desarrollada y, sobre todo, tan rápida como la de HAL, sea incapaz de desempeñar una tarea semejante a un juego de niños. Claro que lo puede hacer. Lo que sucede es que el trabajo de diseñar el molde es nuestro. Una vez que le demos el molde estará encantado de jugar con nuestras palabras para ver cuál de ellas encaja y cuál no.

Un molde para palabras o, en general, para fragmentos de texto, no es otra cosa que lo que, con tecnicismo amenazante, se denomina expresión regular (regular expression, abreviado RE). Crear un molde para que HAL juegue con él es diseñar una expresión regular como argumento de una orden.

Pensará el lector que si la cosa es tan divertida no se entiende a qué vino el calificativo aquél de monstruosidad y el miedo que el autor parece querer infundir en nosotros con tanto ahínco. Espere el lector y verá cómo sus ojos sufren ante estos moldes aparentemente inofensivos.

Una vez determinada la tarea y explicada la forma general de resolverla, vamos a analizarla en sus partes constituyentes.

Necesitamos una orden que busque en un flujo de texto los fragmentos de él que encajan con un molde dado (con una expresión regular). Por tanto, la tarea se divide en dos partes:

  • Elegir la orden con las opciones que corresponda.

  • Diseñar la expresión regular para el caso que nos ocupa.


La primera parte parece sencilla y, sin embargo, puede consumir más tiempo del que suponemos, si no andamos con cuidado.

Sabemos, ciertamente, que grep puede devolvernos las líneas donde se encuentra el fragmento buscado. Pero lo que nosotros queremos es solamente ese fragmento, el resto de la línea donde reside no nos interesa.

En este tipo de situaciones nos encontramos ante un dilema: ¿necesitamos recurrir a otra orden todavía no conocida o hay alguna opción poco común, en las órdenes conocidas y relacionadas con la tarea, que resulte apropiada para resolver el problema? Es fácil caer en la tentación de no plantearse el dilema y lanzarse en pos de una orden nueva, sin medir el tiempo que ello puede consumir. Se trata de un error habitual del que hay protegerse. Todos lo cometemos y seguimos cometiéndolo, como humanos que tropezamos estúpidamente en la misma piedra. Por eso, lo primero que hay que hacer siempre es releer la página de manual de la orden u órdenes que ya conocemos y que están relacionadas con nuestro propósito, por si existe allí alguna opción desconocida y adecuada a él.

Si releemos la página de manual grep(1), descubriremos justamente lo que necesitamos, la opción -o de grep, que devuelve solamente los fragmentos que encajan con la expresión regular en lugar de la línea entera donde está ese fragmento. Una opción poco común de grep, pero que nos viene de perlas.

Tenemos ya la orden. Lo que nos falta es construir el molde o expresión regular pertinente, que pasará a ser el primer argumento de grep [La sintaxis de grep fue comentada en un artículo anterior de la serie]. Nuestra orden tendrá, por tanto, la forma:

grep -o RE

donde RE es la expresión regular que nos toca diseñar.

Tómese el lector un descanso antes de seguir, lo que viene es largo y engorroso. Y va a ser largo, porque el autor ha decidido no presentar directamente la mejor expresión regular que ha encontrado para la situación presente, sino probar con varias hasta llegar a aquélla. De este modo, el lector podrá convencerse por sí mismo de que la verdadera dificultad en el diseño de expresiones regulares no es tanto su aspecto cuanto el proceso heurístico que conduce a su descubrimiento. Diseñar una expresión regular es una actividad intelectual creativa y, en consecuencia, susceptible de varias aproximaciones y de varias soluciones.

[Continúa en la siguiente entrega ...]

No hay comentarios:

Publicar un comentario