Aunque pueda resultar repetitivo, vamos una vez más a repasar nuestra lista de tareas inicial y el estado de su solución:
- Obtener la definición del término hacker que contiene el Jargon File [Terminada]:
dict -d jargon hacker
- Extraer de la definición resultante todas las expresiones entre llaves [Terminada].
grep -E -o '[{][^[:punct:]]+}'
- Crear una lista con el formato adecuado [Sin terminar: modelo creado]:
awk '{print "hacker:"; print " " $1}'
- Guardar esa lista en un fichero [Terminada]:
>jargon_ref
Tenemos incluso la orden completa diseñada [en rojo va lo que queda sin terminar. Nótese además el signo '
\
' que permite escribir en varias líneas una orden muy larga, como ésta y las que vendrán. Lo uso sin más comentario a partir de ahora]:dict -d jargon hacker | grep -E -o '[{][^[:punct:]]+}' \
|awk '{print "hacker:"; print " " $1}'
>jargon_ref
Lo que resta es refinar la orden
awk
para que dé el formato previsto a la salida de grep
.Refinar una solución es el último paso en la culminación de una tarea. Si la tarea es muy compleja puede resultar necesario llevarlo a cabo en una fase intermedia. En cualquier forma, es un procedimiento necesario del que pueden surgir incluso nuevas ideas. Refinar una solución implica en no pocas ocasiones refinar el propio planteamiento del problema y, en consecuencia, ampliar o reformular el esquema de su solución. Una tarea nunca está acabada del todo, del mismo modo que la perfección es utópica y ucrónica. Conviene acostumbrarse a esta idea, porque es central en todo proceso de resolución de problemas.
Consideremos nuestro caso actual, el modelo de instrucción
awk
que esbozamos al principio.Ahora ya sabemos lo que nos devuelve
grep
: una lista de palabras entre llaves, cada una en una línea. Podemos preguntarnos si esta salida se ajusta a lo que awk
espera.Recordemos que
awk
dividía su flujo de entrada en registros y campos [El lector debe releer, si no lo recuerda bien, el artículo precedente sobre el tema]. Dijimos, más o menos de pasada, que, si no se especifica lo contrario, el salto de línea es el indicador del final de un registro y el comienzo del siguiente, o sea, el carácter que separa un registro de otro, y que el espacio cumple idéntica función como separador de los campos. También se recordará que la segunda acción print
de nuestro modelo para la orden awk
imprimía un espacio seguido del primer campo. Por tanto, la salida de grep
:
{hack value}
{cracker}
{the network}
{Internet address}
{hacker ethic}
{bogus}
{geek}
{wannabee}
será analizada por nuestra orden provisional
awk
de modo que cada conjunto de palabras entre llaves será un registro y cada fragmento de texto separado por espacio será un campo. No es de extrañar entonces que nuestra orden sea totalmente inadecuada para nuestro propósito, puesto que para cada registro (cada conjunto de palabras entre llaves) ejecutará las acciones indicadas y producirá este resultado:
hacker:
{hack
hacker:
{cracker}
hacker:
{the
hacker:
{Internet
hacker:
{hacker
hacker:
{bogus}
hacker:
{geek}
hacker:
{wannabee}
Una forma de empezar a resolver el problema es redefinir la variable interna de
awk
que establece el carácter separador de campos. Esta variable recibe el nombre FS
(Field Separator) y su valor por defecto es el espacio.Si el valor de
FS
fuese, en lugar del espacio, el salto de línea, awk
analizaría la lista que produce grep
de forma que cada cada registro fuese una línea de esa lista y, a la vez, el primer y único campo del registro. De esta manera la acción print " " $1
imprimiría por cada línea (por cada registro) el grupo de palabras entre llaves (el campo $1
de cada registro). Y eso es justo lo que nos interesa.En consecuencia, debemos incluir dentro de nuestra orden
awk
una expresión que asigne a FS
el valor del salto de línea. Este tipo de expresión de asignación de valor se escribe así (donde la secuencia '\n
' representa el salto de línea):FS="\n"
awk
nos proporciona la opción -v
para incluir redefiniciones de sus variables internas. Por tanto, podríamos reescribir nuestra orden de la siguiente manera:awk -v FS="\n" '{print "hacker:"; print " " $1}'
Y el resultado de ejecutarla sobre la salida de
grep
:grep -E -o '[{][^[:punct:]]+}' jargon_hacker \
| awk -v FS="\n" '{print "hacker:"; print " " $1}'
sería éste:
hacker:
{hack value}
hacker:
{cracker}
hacker:
{the network}
hacker:
{Internet address}
hacker:
{hacker ethic}
hacker:
{bogus}
hacker:
{geek}
hacker:
{wannabee}
Es obvio, que queda un problema, lograr que la acción
print "hacker:"
se ejecute una sola vez y al principio. Tal como está ahora en nuestra orden provisional este print
se ejecuta para cada registro (para cada línea de la salida de grep
), con el resultado anterior, aún insatisfactorio.Podemos conseguir el comportamiento deseado (imprimir una sola vez y al principio la expresión "hacker:"), si situamos la acción
print "hacker:"
en un bloque de acciones especial que se ejecuta antes de que awk
analice su flujo de entrada. Este bloque de acciones se escribe delante del bloque de las restantes acciones precedido por la palabra BEGIN
:BEGIN {acciones_iniciales} {resto de acciones}
Este bloque inicial es, además, el sitio ideal para introducir las redefiniciones que establezcamos de las variables internas de
awk
---al fin y al cabo redefinir es también un tipo de acción inicial. Es pues el mejor lugar para poner ahí nuestro FS="\n"
, con lo que nos evitamos situarlo como el argumento de la opción -v
, que resulta, en consecuencia, innecesaria.La orden
awk
nuevamente modificada quedaría de la siguiente manera:awk 'BEGIN {FS="\n"; print "hacker:"} {print " " $1}'
No viene mal resumir, como colofón, los pasos que sigue
awk
para procesar su argumento:- Ejecuta el bloque de acciones que empieza con
BEGIN
:- Pone la variable
FS
---que define el carácter que separa los campos--- al valor '\n
' (salto de línea). - Imprime la expresión "hacker:".
- Pone la variable
- Analiza el flujo de entrada:
- El valor de
RS
(separador de registros) no está modificado en el bloqueBEGIN
: los registros son las líneas del flujo entrante. - El valor de
FS
(separador de campos) está modificado en el bloqueBEGIN
: los campos son las líneas del flujo entrante. - Ejecuta el bloque de acciones restantes sobre cada registro:
- Imprime espacio seguido del valor del primer campo del primer registro.
- Repite n veces la acción anterior hasta agotar todos los registros.
- Devuelve el resultado al dispositivo de salida.
Es hora de poner a prueba la orden remozada:
grep -E -o '[{][^[:punct:]]+}' jargon_hacker \
| awk 'BEGIN {FS="\n"; print "hacker:"} { print " " $1 }'
El resultado, finalmente, cumple nuestras expectativas:
hacker:
{hack value}
{cracker}
{the network}
{Internet address}
{hacker ethic}
{bogus}
{geek}
{wannabee}
Tarea terminada, sí. Podemos felicitarnos. Hasta puede que HAL guste en su interior del sabor de nuestra cocina, quién sabe ;-)
Mas, si sintiese algo parecido, seguro que nos reprocharía no haber sacado más beneficio de nuestro esfuerzo. Tal vez la tarea esté abierta todavía a mejoras no sospechadas. Lo veremos a continuación ... [el próximo día]
Resumen:
- Para disponer una orden en varias líneas se usa el signo '
\
' como señal de final de línea. Es útil cuando la orden es muy larga y queremos que sea fácil de leer y escribir. - La secuencia de caracteres '
\n
' representa el salto de línea. - Parte esencial en la resolución de problemas es el proceso de refinar las soluciones provisionales o modelos de solución obtenidos en las estadios iniciales de dicho proceso.
- Los valores de las variables internas de
awk
son susceptibles de recibir asignaciones diferentes a las establecidas por defecto. Tal es el caso, por ejemplo, de las variablesRS
yFS
que especifican las caracteres queawk
interpreta como separador de registros y separador de campos, respectivamente. awk
permite definir acciones iniciales, que se ejecutan con anterioridad al análisis que se realiza del flujo de texto entrante. Tales acciones se identifican con un bloque preliminar de acciones precedido de la palabra claveBEGIN
No hay comentarios:
Publicar un comentario