viernes, 27 de febrero de 2009

HAL y vim (III)

De las opciones del menú "Archivo" hemos comentado todas menos la última, la de imprimir. Que un editor de texto proporcione esta opción no deja de ser curioso. Como dijimos al principio de estos artículos sobre Vim, un editor no debería en sentido estricto preocuparse por el destino final del documento que se edita. En una buena división de tareas, la presentación física del documento y, en consecuencia, su preparación para una salida impresa, debería ser responsabilidad de otras aplicaciones. El editor le ha de servir al escritor únicamente como herramienta para redactar su trabajo y definir, mediante algún sistema estipulado de marcas, su estructura lógica. El propio escritor o aquél en quien delegase tendría, después, que recurrir a otra u otras herramientas de preparación de documentos (diseñadores y tipógrafos electrónicos) que pudiesen interpretar sus marcas y producir una salida impresa de calidad.

Ahora bien, en documentos rápidos, donde el escritor no tiene tiempo o ganas de utilizar marcas que interpretarán los diseñadores o tipógrafos y donde, a pesar de todo, le gustaría ver una rápida copia impresa, el editor puede tener a bien convertirse en intermediario de los especialistas en la preparación de documentos, que obrarán entonces de un modo relativamente tosco según su nivel real de pericia, pero suficiente para estos deseos precipitados y poco exigentes.

Tal es el sentido de una opción para imprimir en un editor como Vim. Se trata de una opción de uso poco frecuente. De hecho, es raro verla comentada en los manuales sobre Vim. Sin embargo, y puesto que nosotros hemos tomado como hilo conductor las opciones de un procesador de texto, nos topamos de bruces con ella, como si se tratase de lo más natural del mundo, cuando, en realidad, resulta ser un artefacto realmente exótico. A pesar de esta rareza y de la filosofía inicial de esta serie de artículos, nos vemos obligados a referirla, con el fin de no dejar un hueco en la exposición. Tenga en cuenta el lector, no obstante, que no es de buen gusto conformarse con el trabajo rudimentario que Vim puede hacer en este caso. Vim esta diseñado para editar, y nada más. El usuario debería aprender en su momento a recurrir por sí mismo a los especialistas en preparación de documentos y conocer el sistema de marcas con el que etiquetar su texto para que aquéllos obren en plenitud de sus facultades.

Hecha la necesaria aclaración, es muy fácil pedir a Vim que se encargue de devolvernos una copia impresa del documento actual:

:hardcopy

Esta orden enviará el documento actual a la impresora establecida por defecto. Naturalmente, se presupone que la infraestructura para que funcione la impresora está a punto, cosa que debería suceder tras una instalación genérica de una distribución GNU/Linux.

Si en lugar de enviar directamente a la impresora el documento actual, se prefiere guardarlo en un fichero, se puede utilizar en su lugar esta otra orden:

:hardcopy >nombre_fichero.ps

Nótese aquí el operador de redirección, que ya conocemos y que tiene exactamente el significado esperado. El nombre de fichero es, obviamente, el que el usuario decida, su extensión .ps es también opcional, aunque conveniente, puesto que define explícitamente el tipo de fichero que se generará, un fichero PostScript.

Pongamos a prueba la orden con un texto existente. Por ejemplo, con un fragmento de la ayuda en línea de Vim. Sí, lector, como es costumbre en el mundo de HAL, Vim proporciona una gigantesca ayuda en línea a la que se puede acceder con la orden:

:help

No es propósito de estos artículos de iniciación tratar de este extraordinario sistema de ayuda y de todas sus sutilezas. Queda al arbitrio del lector investigar ahí por su cuenta.

El fragmento de la ayuda de Vim que seleccionaremos para imprimir puede ser, por qué no, la sección relativa a la impresión, a la que se accede mediante la orden:

:help printing

Ahí tenemos una página visible de la sección entera sobre este tema. Hagamos que Vim llame al especialista en preparación de documentos y nos produzca una copia impresa de la sección completa, que guardaremos en el fichero vim_ayuda_imprimir.ps:

:hardcopy >vim_ayuda_imprimir.ps

Este fichero puede abrirse pidiendo a HAL que utilice un visor de documentos como okular (en entornos Kde) o evince (en entornos Gnome o Xfce):

okular vim_ayuda_imprimir.ps

Pero, en lugar de salir de Vim ---o enviarlo al trasfondo con 'Ctrl+Z'--- para poder ejecutar esta orden, vamos a hacerlo desde dentro del propio Vim. Ello es posible gracias a un tipo especial de orden ex, que se expresa mediante un signo de admiración (!). Es el medio de que disponemos para advertirle a Vim algo parecido a esto:

¡Eh, Vim, quiero enviar a HAL la siguiente orden! ...

Por ejemplo, dentro de la interfaz de Vim puedo pedir a HAL que me diga mi nombre de usuario:

:!whoami

El resultado se mostrará en la parte inferior de la interfaz de Vim. Después de pulsar 'Enter', regresaremos exactamente al mismo punto en el que estábamos antes de la ejecución de la orden.

Este peculiar dispositivo de Vim para interactuar con HAL desde dentro de él tiene asombrosas aplicaciones. Aquí no vamos a tratarlas. Pero el autor no se resiste a proponer al menos una, algo más complicada que la anterior. Imaginemos que estamos editando un texto y queremos introducir en él la hora exacta de edición. Podemos hacerlo directamente desde Vim, utilizando la orden :r (abreviatura de :read), que lee su argumento (un fichero o la salida de una orden de HAL) y lo introduce en el documento actual:

:r ! date | cut -d' ' -f4

Esta orden escribirá, por defecto, la hora actual en la línea siguiente a la que se está editando. Como se ve, es sintácticamente análoga a la orden :wq, vista el día pasado. En el caso actual se ejecutan dos operaciones: la que realiza :!date | cut -d' ' -f4 y la que realiza :r. Los espacios entre las órdenes ex son opcionales; no así, como sabemos, los espacios existentes ente el nombre de la orden (aquí, una orden de HAL) y sus argumentos. En consecuencia, podíamos haberla escrito también de esta otra forma menos legible:

:r!date | cut -d' ' -f4

Volvamos a nuestra humilde y elemental orden para visualizar el fichero vim_ayuda_imprimir.ps y hagamos que HAL la ejecute desde Vim

:okular vim_ayuda_imprimir.ps

He aquí la primera página del documento visualizado, listo para imprimir:




Resumen

  • La orden :hardcopy permite enviar a la impresora el documento actual. El resultado de :hardcopy se puede redirigir a un fichero postscript, en lugar de a la impresora, mediante la orden :hardacopy >nombre-fichero.

  • Vim contiene una ayuda en línea muy completa sobre su uso, a la cual se accede mediante la orden :help.

  • Para acceder a las órdenes de HAL desde dentro de Vim se utiliza la orden :!orden_de_HAL, donde orden_de_HAL es una orden que HAL entiende.

  • La orden :r, abreviatura de :read, lee el fichero que se le da como argumento y lo introduce en el documento que se está editando. Una variante común de esta orden es la que recibe como argumento la salida de una orden de HAL. Por ejemplo, :r ! date introduce en el documento que se está editando la salida de la orden date de HAL.

jueves, 26 de febrero de 2009

HAL y vim (II)

El día pasado nos quedamos a punto de revelar el secreto de Vim, su idea genial. Como, a pesar de ser particularmente original, esta idea es bien simple, podemos resumirla en pocas palabras.

Editar un texto implica dos actividades diferentes: la de escribirlo (mecanografiarlo) y la de modificar lo escrito o, en general, cualquier otra actividad no relacionada directamente con la escritura, como las de cerrar o abrir el editor mismo. La interfaz de un editor tiene que contemplar esta diferencia y para cada una de estas actividades proporciona medios diferentes. Hoy estamos acostumbrados a que la acción de modificar y, en general, cualquier otra acción que no sea escribir, se realice mediante el ratón, pero también existe la posibilidad de utilizar, en su lugar, combinaciones especiales de teclas, los llamados "atajos" en los editores gráficos. Por ejemplo, la combinación 'Ctrl+C' es frecuente cuando se trata de copiar un fragmento de texto previamente seleccionado; por su parte, la combinación 'Ctrl+Q' sirve para cerrar la aplicación. El editor de consola Emacs utiliza esta misma técnica, si bien con un grado de elaboración mucho mayor que en un editor gráfico. El problema de esta aproximación es que, aun proporcionando, en general, mayor rapidez que el simple uso del ratón, obliga a las manos a abandonar el conjunto de teclas habituales para la escritura, que es, además, el más próximo a ellas, con la pérdida consiguiente de velocidad y la sobrecarga física añadida.

¿Por qué no utilizar las mismas teclas con las que habitualmente escribimos para realizar el resto de operaciones? Si dispusiésemos de varios teclados sería factible, simplemente asignando a cada teclado una función específica y cambiando de uno a otro cuando fuera necesario. Naturalmente, esto lentificaría el proceso de la edición tanto o más que el uso del ratón. Pero ¿y si no necesitásemos teclados físicamente diferentes, sino múltiples teclados virtuales funcionando sobre un mismo teclado físico? Los problemas anteriormente descritos se resolverían de un plumazo y la rapidez y naturalidad de la edición mediante este procedimiento no tendrían parangón. Esta idea, tan extraña como sencilla, es la idea original de Vim.

Consideremos nuestro teclado físico como el soporte de varios teclados virtuales (modos los denomina la literatura técnica sobre Vim). Estos modos o teclados son básicamente dos: el teclado con el que introducir o insertar texto y el teclado para realizar el resto de operaciones de modificación del texto o de manipulación de la aplicación, llamémoslo teclado de órdenes, porque es el que permite solicitar al editor que ejecute distintas funciones sobre lo escrito o sobre su propio estado. Consideremos también que el teclado virtual que se presenta de entrada es el teclado de órdenes. Naturalmente, necesitaremos de un mecanismo para cambiar este teclado por el teclado dedicado a la inserción del texto. Este mecanismo es ---cómo no--- una tecla, la tecla 'i', de insertar. También necesitamos de un procedimiento análogo para regresar al teclado inicial, el de las órdenes, es decir, para escapar del teclado de inserción y volver a la situación inicial. En este último caso, la tecla pertinente es la tecla 'Esc'. Por tanto, las teclas 'i' y 'Esc' tienen un significado especial dependiendo del teclado virtual o modo activo en el momento en que se pulsan. Son, respectivamente, el dispositivo de conmutación del teclado de órdenes al teclado de inserción (tecla 'i'), y viceversa (tecla 'Esc').

Con este bagaje previo podemos continuar nuestra exploración de los procedimientos que suministra Vim para realizar las funciones típicas del menú "Archivo" especificadas en el artículo precedente. Entonces vimos cómo abrir un fichero. Ahora toca ver, en primer lugar, cómo guardar en el disco duro el contenido editado.

Volvemos a abrir nuestro editor con un fichero de prueba:

vim primera_prueba

Ahora, antes de escribir nada en él, tenemos que conmutar el modo de órdenes, el teclado virtual que está activo en el momento en que se inicia Vim, por el modo o teclado para insertar texto. Para ello pulsamos la tecla 'i' o, lo que es lo mismo, solicitamos a Vim que queremos insertar texto. En la parte inferior de la interfaz de Vim aparecerá seguramente una indicación de que nuestro modo activo después de que Vim ha cumplido nuestra orden es el de inserción:

-- INSERTAR --

Nótese cómo Vim funciona en este sentido de la misma manera que el propio HAL. Vim atiende nuestras solicitudes, que le enviamos desde el teclado, cumpliéndolas como si se tratase de órdenes. Al pulsar 'i' en el modo o teclado de órdenes, estamos haciendo algo muy parecido a lo que hacemos cuando introducimos una orden en la línea de órdenes de HAL, estamos diciéndole a Vim:

Vim, inicia el modo de inserción

Es más, aunque en el caso de estas órdenes absolutamente abreviadas, como la orden 'i', no vemos, por defecto la orden que tecleamos, sino sólo su resultado, existe siempre la posibilidad de introducir órdenes extendidas alternativas, que sí veremos (lo que los entendidos denominan órdenes ex, en honor del editor ex, que habita en el subsuelo de Vim). En muchos casos estas órdenes más largas ---al menos constan de dos caracteres--- son las únicas disponibles y las usaremos profusamente.

Conviene detenerse en este punto crucial, el de las órdenes ex, porque, entre otras cosas, es como mejor se entiende la semejanza entre HAL y Vim.

Una orden ex se inicia con el carácter ':' (dos puntos), tecleado en el modo o teclado de órdenes. Si el lector no ha tocado Vim desde que lo dejamos ahí dispuesto para introducir nuestro texto, debe pasar ahora mismo al modo de órdenes, mediante la tecla 'Esc'.

Activado el teclado de órdenes, tecleamos ':' (dos puntos). El carácter se reproducirá en la parte inferior de la interfaz de Vim. Esta parte inferior, llamada línea de estado, a la que acabamos de acceder al teclear los dos puntos, es semejante a la línea de órdenes de HAL. En este caso es Vim quien está esperando a que le introduzcamos una orden. Nuestra orden va a ser la de antes:

Vim, inicia el modo inserción

que, mediante una orden ex, se expresa de la siguiente manera:

startinsert

Esta orden aparecerá a nuestra vista inmediatamente después de los dos puntos. Al pulsar 'Enter', Vim ejecutará la orden y pasaremos al modo inserción, lo que nos notificará mostrando el texto -- INSERTAR -- en la línea de estado, citada antes. Las órdenes ex también admiten abreviaturas. Así, en lugar de startinsert podemos usar la forma abreviada:

starti

O, incluso:

star

Hemos regresado al modo de inserción, esta vez por vía de una orden ex. (Naturalmente siempre usaremos para cambiar de modo la orden corta 'i'. Aquí una orden ex carece de sentido práctico, como en cualquier otra situación en que existe una forma directa de introducir la orden). Podemos, pues, empezar a escribir nuestro texto. Escribamos, por ejemplo, lo siguiente:

Yo soy yo y tú eres Vim

Nuestro último paso es guardar el fichero recién editado.

En primer lugar conmutamos al modo de órdenes con 'Esc' y desde ahí tecleamos la orden que Vim reconocerá para escribir o guardar la modificación en el disco duro, de nuevo una orden ex, abreviatura de la más larga :write (escribir):

:w

Tras pulsar Enter, Vim nos muestra en la línea de estado el resultado de la ejecución de la orden:

"primera_prueba" [Nuevo] 1L, 25C escritos

Que quiere decir que se ha escrito en el disco el nuevo fichero primera_prueba, el cual consta de 1 línea y 25 caracteres.

El editor sigue ahí con el texto actual del fichero primera_prueba. Podemos continuar realizando la siguiente operación, la de "Guardar como". Vamos o modificar ligeramente el fichero primera_prueba. Escribimos, tras cambiar de modo con 'i', lo siguiente:

Adiós, Vim

Pulsamos 'Esc' para volver al modo de órdenes y tecleamos esta nueva orden:

:w segunda_prueba

Vim volverá a mostrar el resultado de su ejecución, que no es otro que el de guardar la modificación reciente en el fichero segunda_prueba. Adviértase que, sin embargo, el fichero primera_prueba no se modifica a menos que guardemos los cambios en él [Nota: ver mi primer comentario a este artículo].

Voilà! Operación terminada.

Nos queda simplemente salir de la aplicación. Como no vamos a ver en estas sesiones sobre Vim ningún ejemplo de edición de varios ficheros simultáneos ---nos limitaremos a operaciones muy básicas---, las opciones de "Cerrar" y "Salir", típicas de los procesadores y editores de escritorio, son equivalentes.

Existen dos modos de salir. Salir tras guardar los cambios:

:q

que es una abreviatura de:

:quit

y salir sin guardar ningún cambio, opción que nos viene de perlas ahora, para dejar intacto el fichero primera_prueba. (Recuérdese que la última modificación quedó guardada en el fichero segunda_prueba):

:q!

Cuando de lo que se trata es de guardar cambios y salir en un solo golpe, es común usar una orden doble simultánea:

:wq

Enhorabuena, lector, acabas de finalizar tu primera sesión de edición con Vim. Parece endiabladamente complicado. No lo es en absoluto. Tu sensación de mareo es únicamente atribuible a la falta de costumbre. Los usuarios de Vim ---el autor entre ellos--- aseguran que, una vez adquirido el nuevo hábito, es difícil encontrar nada que pueda competir con Vim en rapidez de edición y en versatilidad. La única prevención que hay que tomar es pulsar 'Esc' en los casos en que no se tenga seguridad de qué modo está activo y se quiera emitir una orden para Vim. 'Esc' se puede pulsar cuantas veces se quiera: mil pulsaciones equivalen a una. Así es que, sin miedo, pulsa 'Esc' a discreción cuando vayas a introducir órdenes, ésas que con tanta celeridad Vim cumplirá a las mil maravillas.

Antes del merecido resumen puede ser útil exponer de nuevo el proceso de esta edición simple, iniciado el día pasado, en la forma de un diálogo con Vim [Seguimos las mismas convenciones tipográficas indicadas aquí.]

YO:
HAL, pídele a 'vim' que abra el fichero 'primera_prueba':

[Esta orden es la única que se ejecuta desde la línea de órdenes de HAL, el resto de órdenes se dirigirán a 'vim' desde su propia interfaz.]

vim primera_prueba


HAL:

[HAL nos presenta la interfaz de 'vim'.]

YO:
Vim, inicia el modo inserción:

i


VIM:

[Vim cambia al modo inserción y nos lo notifica en su línea de estado:]

-- INSERTAR --


YO:

[El usuario escribe la siguiente línea: 'Yo soy yo y tú eres Vim'.]

Vim, sal del modo inserción y vuelve al modo de órdenes:

Esc


VIM:

[Vim vuelve al modo de órdenes.]

YO:
Vim, guarda la modificación realizada en el disco.

:w


VIM:

[Vim guarda lo editado y notifica el resultado de la acción en su línea de estado.]

"primera_prueba" [Nuevo] 1L, 25C escritos


YO:
Vim, inicia el modo inserción:

i


VIM:

[Vim cambia al modo inserción y nos lo notifica en su línea de estado:]

-- INSERTAR --


YO:

[El usuario añade lo siguiente a la línea actual: 'Adios, Vim'.]

Vim, sal del modo inserción y vuelve al modo de órdenes:

Esc


VIM:

[Vim vuelve al modo de órdenes.]

YO:
Vim, guarda la modificación realizada en el disco con el nombre 'segunda_prueba':

:w segunda_prueba


VIM:

[Vim guarda la modificación y notifica el resultado de la acción.]

"segunda_prueba" [Nuevo] 1L, 37C escritos


YO:
Adios, vim, y no guardes los últimos cambios en el fichero 'primera_prueba':

:q!




Resumen

  • La idea original de Vim es distinguir modos de operación con el mismo teclado: un modo de inserción para escribir texto y un modo de órdenes para modificarlo o realizar cualquier operación distinta a la escritura. Estos modos funcionan como sendos teclados virtuales sobre un mismo teclado físico.

  • La orden 'i' de Vim cambia al modo inserción.

  • La orden 'Esc' de Vim cambia al modo de órdenes. Es la única orden que puede ejecutarse cuando el modo activo es el de inserción.

  • Existen dos formas de órdenes posibles para Vim, las órdenes que se producen pulsando directamente teclas convencionales en el modo de órdenes y las órdenes ex, que se inician tecleando el carácter ':' (dos puntos) seguido de la orden propiamente dicha. Por ejemplo, :w es una orden ex de Vim.

  • La orden :w de Vim, abreviatura de la orden :write, permite escribir o guardar en disco el resultado de la edición. Cuando va seguida de un nombre de fichero guarda dicho resultado en el fichero de ese nombre.

  • La orden :q de Vim, abreviatura de la orden :quit, sale de Vim. Para salir sin guardar cambios la orden es :q!. Para escribir el resultado en disco y salir en un solo paso la orden es :wq.

  • La tecla 'Esc' es la medicina del usuario de Vim para los patologías frecuentes de confusión de modos.

miércoles, 25 de febrero de 2009

HAL y vim (I)

Una de las tareas básicas que realiza ordinariamente el usuario de una computadora es escribir textos. En este sentido, la computadora ha aliviado hasta límites insospechados el trabajo antaño penoso del mecanógrafo, un trabajo que la mayoría de los que escriben ---sea una carta, una receta de cocina, un artículo de investigación o una novela--- han tenido que asumir desde el principio como parte inevitable de su actividad.

Ahora bien, desde el surgimiento y dominio de la interfaz gráfica de usuario y los entornos de escritorio, a la vez que esta indudable mejora se ha introducido subrepticiamente una nueva e inesperada carga. Ya no se le pide al autor del texto que simplemente lo edite, esto es, que lo redacte y purifique de cuantos errores detecte su ojo. Se presupone, además, que debe darle la forma exacta que habrá de tener en su presentación en papel o, cada vez más, en pantalla. Es decir, el escritor se ha visto obligado a asumir, de golpe y porrazo, las funciones del diseñador y del tipógrafo. No otra cosa es lo que hace cuando decide el tipo de fuente, las dimensiones de la página y sus componentes y un largo etcétera de detalles que determinan el aspecto que el texto acabará teniendo en su versión impresa o navegable, cosas que hasta hace bien poco eran competencia exclusiva de los expertos en la materia.

Hasta tal punto se ha convertido en algo natural e incuestionable este peculiar suplemento del trabajo de la escritura ---fuente de incontables sufrimientos para el escritor, dicho sea de paso---, que pocos usuarios conciben escribir un texto con algo que no sea un procesador de textos (MS Word, Openoffice Writer, etc.) o, no contenga funciones de procesamiento de texto. Hoy por hoy, requiere ya de introducción previa, como la que estamos haciendo, determinar claramente la diferencia entre editar un texto y todo lo otro que, en estos momentos, rodea al acto de la edición propiamente dicha y que podríamos englobar en la noción de dar formato al texto.

¿Qué es, pues, un editor de textos? Suprima el lector de su programa de procesamiento de textos todas las funciones relativas a lo que hemos llamado ---y suelen llamar tales programas--- dar formato y se quedará con la mitad de las opciones existentes en el menú de su aplicación. Ésas y no otras son las opciones principales que proporciona convencionalmente una aplicación para la edición de textos.

Vi es un clásico editor de textos para la consola y está provisto de todas las opciones básicas de edición y otras muchas más de las que no trataremos. Nuestro objetivo es presentar Vim, que es una versión mejorada de Vi y que se convertirá a partir de ahora en el experto ayudante que HAL pone en nuestras manos para estos menesteres. Aunque prácticamente no veremos casi nada de Vim que no tenga ya el propio Vi, esas mejoras, de las que se pueda sacar partido en el futuro, hacen que Vim sea recomendable como primera elección. En lo que sigue y para simplificar la exposición, hablaremos siempre de Vim, también en los casos en que, rigurosamente hablando, nuestras afirmaciones debieran atribuirse propiamente a Vi.

Tendremos, por tanto y en primer lugar, que instalar Vim en nuestro sistema. Las distribuciones modernas suelen incluir por defecto una versión reducida de Vim, pero es muy posible que una versión más completa no esté instalada. El procedimiento de instalación es bien conocido ya a estas alturas:

aptitude install vim

Antes de empezar a trabajar con Vim conviene tener en cuenta unas pocas cosas más.

Intentar explicar Vim para usuarios que no están acostumbrados a las aplicaciones de consola y, ni tan siquiera, al uso de un editor puro y simple, entraña más de una dificultad. Una manera posible de empezar salvando tales dificultades es tomar como punto de partida las opciones fundamentales de un procesador de texto que ha sido expurgado de las funciones que no son características de los editores y ver cómo obtener el mismo resultado con Vim. Este es el camino que vamos a elegir como hilo conductor de la exposición.

Por otra parte, y puesto que la forma de operar con la versión "pura" de Vim ---su versión para consola y sin uso alguno de ratón, que es de la que hablaremos--- es radicalmente diferente de la forma de operar en cualquier editor de interfaz gráfica, será necesario detenerse en las peculiaridades de la interfaz de este Vim "puro y duro" que vayan surgiendo por el camino. Eso sí, una vez explicada la característica en cuestión se sobreentenderá su conocimiento y su sentido para el resto de situaciones semejantes.

Finalmente ---y es fundamental advertirlo---, es improbable que el lector se haga una idea efectiva del contenido de la explicación si no se ejercita con ejemplos de su invención. No es algo privativo de Vim, ni de las aplicaciones de consola en general, sino que es consustancial al aprendizaje de cualquier aplicación nueva, sea del tipo que sea.

Pongamos término al preámbulo y entremos decididamente en materia. Merece la pena dar un primer bocado antes de terminar la sesión de hoy. Vamos a hacerlo como sugerimos, tomando como hilo de Ariadna los menús típicos de un procesador de textos, por ejemplo, de Openoffice Writer (versión 2.4).

La primera entrada de menú que encontramos allí es el clásico menú "Archivo". Si nos ceñimos a las opciones básicas del menú "Archivo" de Openoffice Writer ---comunes a la mayoría de procesadores de texto o editores--- encontraremos las siguientes [Se indica el nombre de la opción y la función asociada]:

Nuevo
Abrir un nuevo fichero.

Abrir
Abrir un fichero existente.

Cerrar
Cerrar la ventana de edición del fichero actual, sin cerrar la aplicación.

Guardar
Guardar en el disco duro los cambios realizados en el fichero editado.

Guardar como
Guardar en el disco duro bajo un determinado nombre de fichero los cambios realizados en el fichero editado. Esta función se usa cuando se quiere dar otro nombre al fichero modificado que el que éste tenía en origen.

Imprimir
Enviar el fichero a la impresora

Terminar
Cerrar la aplicación


Tenemos, en resumen, cuatro operaciones básicas: abrir o cerrar un fichero, guardar los cambios realizados en él e imprimirlo. Veamos cómo se realiza cada una de estas operaciones con Vim. Propondremos una sola forma de hacerlo. Normalmente, Vim permite hacer la misma operación de varias maneras distintas. Como aquí de lo que se trata es de ofrecer una introducción absolutamente reducida a lo elemental, adoptamos, en general y salvo excepciones, esta misma constricción para ulteriores explicaciones.

Abrir un fichero de texto en Vim se hace simplemente desde la línea de órdenes, añadiendo como argumento a la orden vim el nombre que queramos dar al fichero (cuando es de nueva creación) o la ruta del fichero (cuando es un fichero ya existente). Es convención de sistemas Unix evitar espacios en nombres de ficheros. En general y aunque nuestro HAL puede tratar perfectamente con nombres que incluyen espacios, nos ahorraremos trabajo si sustituimos el espacio por un guión o un punto. Por ejemplo:

vim primera_prueba

abre Vim para editar el nuevo fichero de nombre primera_prueba.

Por su parte,

vim guiones/buscar_en_jargon

abre Vim para editar el fichero existente buscar_en_jargon, que reside en el subdirectorio guiones bajo nuestro home, que se supone que es donde estamos.

Tras esta operación inicial tendremos delante nuestra flamante interfaz de Vim con el espacio de trabajo para editar el fichero abierto. Moverse por esta interfaz es fácil si se conoce su secreto y se está acostumbrado a él. Es sencillamente imposible o infernal si el secreto no ha sido desvelado. Hay quien cree que el conocimiento de este secreto es un signo definitivo de distinción. El usuario de Vim, resulta a ojos extraños un mago ejerciendo su arte en oscuros mundos. Pero hay un truco, y como todos los trucos puede enseñarse y aprenderse. Aprendido el truco, todo su carácter mágico se expone a la luz de la inteligibilidad y adquiere su pleno sentido. La magia deviene entonces razón, aunque su carácter extraordinario y maravilloso no se pierda, sino que más bien se acrecienta en el resplandor de la genial idea subyacente.

Esta genialidad es tan sencilla como revolucionaria. Y lo sigue siendo a pesar de los más de treinta años transcurridos desde su invención. La veremos el día que viene.

Pero no podemos dejar huérfano al lector con su interfaz de Vim abierta. Le diremos cómo salir, aunque la explicación del porqué de esta operación quede reservada para la siguiente entrada de esta serie.

Para salir de Vim hay que teclar lo siguiente:

:q

Por cierto, un truco no comentado hasta ahora. Tras salir de Vim, la combinación de teclas Ctrl+L pondrá el "mensaje de bienvenida de HAL" en la parte superior de la pantalla. Esta combinación es de uso frecuente, sobre todo después de salir de la interfaz de un programa externo a la línea de órdenes.



Resumen

  • Un procesador de textos combina funciones de edición con funciones de formateo.

  • Dar formato es algo que supone una carga innecesaria para quien escribe o edita un texto.

  • La orden vim ejecuta Vim, un magnífico editor de texto para la consola y, en consecuencia, un superayudante de HAL.

  • vim nombre_fichero abre el fichero dado como argumento. Este argumento es el nombre de un fichero o su ruta en el árbol de directorios.

  • La combinación de teclas Ctrl+L permite ubicar la línea actual en la parte superior de la pantalla. Se dice también que Ctrl+L "limpia" la pantalla.

viernes, 20 de febrero de 2009

Aplicaciones para HAL

Los artículos precedentes de esta serie han girado en torno a la gramática fundamental de la lengua de HAL (la primera parte de la serie) y se han adentrado luego, tras un breve ejercicio de lectura, en un terreno más fructífero, pero potencialmente más complejo, el de la creación de guiones. De paso hemos ido conociendo algunas pocas características señaladas del universo de HAL.

Es un camino natural en el aprendizaje de cualquier lengua. Después de conocer las primeras letras y probarlas en frases cortas, se leen los escritos de otros y, posteriormente, se redactan textos originales de complejidad y expresividad crecientes. Y también, como quien no quiere la cosa, se va tomando contacto con el mundo mental y cultural que tal lengua específica busca transmitir.

Podríamos seguir ahondando en el aspecto de la escritura. De hecho, sólo hemos pisado su umbral. Pero el periplo no tiene fin o el fin se pierde en la lejanía, que para el caso es lo mismo.

Sin descartar un retorno a esa fascinante aventura, conviene, no obstante, cambiar por completo el rumbo de nuestras conversaciones y dirigirse a otros lares, al mundo de las aplicaciones de consola. Y es conveniente, porque la mayoría de los usuarios tiende a ver su máquina como un conjunto de aplicaciones con funciones predefinidas, útiles para realizar sus tareas habituales, antes de como un medio programable para cualquier tipo de tarea. Incidir demasiado en la flexibilidad de la lengua de HAL en detrimento del estudio de las aplicaciones puede provocar ---si no lo ha hecho ya--- hastío en el principiante, que quizá no ha vislumbrado todavía la utilidad de la consola en sus cosas del día a día.

Por tanto, los próximos artículos tratarán de presentar aplicaciones de consola diseñadas para la realización de las tareas más comunes. Y, en buena lógica, podrían haber sido y pueden ser las primeras exposiciones a la consola para un principiante.

Ahora bien, aunque el mundo de la consola dispone de estupendas aplicaciones para todos los usos comunes, el número de ellas es demasiado grande. Nadie puede pretender verlas todas. Es necesario adoptar una decisión inicial: elegir unas cuantas como representativas. Naturalmente la elección es subjetiva y muchas de las que presentaremos aquí tienen hermanas igualmente competentes y adecuadas, de las que desgraciadamente no podremos hablar.

El segundo problema es que cada una de estas aplicaciones suele constituir por sí sola un complejo mundo. En muchas casos es impracticable pretender abarcar un conjunto suficientemente amplio de sus funciones y de sus posibilidades, no sólo para el que las explica, sino también para quien quiere usarlas, que se vería completamente desbordado si tuviese ante sí el panorama completo de lo que se puede hacer. Habrá que limitarse, pues, enormemente y centrarse exclusivamente en las funciones absolutamente básicas.

Por otra parte, hay infinidad de manuales, libros y materiales de todo tipo, tanto en la web como en formato impreso, sobre las aplicaciones más importantes. Es absurdo considerar la posibilidad de producir más o mejor que lo ya hecho y documentado.

¿Cuál es, pues, el sentido de escribir otra nueva versión de, por ejemplo, "Una introducción al editor Vim"? El sentido está en el planteamiento. La mayoría de los documentos escritos sobre aplicaciones de consola están dirigidos a usuarios de consola, que saben de qué va la consola y que no se van a desmayar cuando vean ristras enormes de instrucciones de configuración y de secuencias de teclado. Un principiante tendría que tener mucha paciencia y energía, aparte de tiempo libre, para abordar manuales de ese tipo, donde casi todo se da por sobrentendido. Nuestra propuesta es realizar algo así como una introducción de la introducción, en la cual quede explicado suficientemente lo más básico y donde se ofrezcan al principiante recursos para empezar a manejar la aplicación del caso en sus funciones absolutamente elementales y para que pueda, si así lo quiere, entregarse posteriormente a un estudio más profundo a través de la documentación existente.

Aclarada la idea que regirá la exposición de las aplicaciones que se presentarán, puede ser útil esbozar la estrategia que seguiremos en general para desplegar dicha idea. Consta de las siguientes fases:

  1. Definir con antelación y con precisión el uso que se va a hacer de la aplicación.

  2. Configurar la aplicación ---cuando sea necesario--- para cumplir únicamente el uso que se le va a dar.

  3. Conocer la interfaz de la aplicación lo suficiente como para poder llevar a cabo las necesidades previamente definidas y sólo esas.

  4. Aprender únicamente las funciones y órdenes requeridas para cumplir dichas necesidades.



El prefacio está escrito y las aplicaciones ahí delante, cual niños inquietos, levantando la mano ansiosamente para ser los primeros en salir a escena. Hasta la próxima, chavales.

jueves, 19 de febrero de 2009

HAL, awk, el entorno y otras disquisiciones

El día pasado nos quedamos con el siguiente problema sin resolver:

awk 'BEGIN {FS="\n"; print ???? ":"} { print " " $1 }'

En lugar de ???? deberá haber una variable que contenga el valor de lo que damos como primer argumento a nuestra orden ./buscar_en_jargon. Sabemos que esta variable no puede ser simplemente $1, pues ella tiene ya un significado especial para awk y, dentro de awk pierde su significado como parámetro posicional.

El problema, que, en un principio, parece conducirnos a un callejón sin salida, puede afrontarse gracias al diseño de awk, que permite acceder a variables externas a él mismo, gracias a un recurso suyo especialmente diseñado para ese fin. Este recurso es, sencillamente, otra variable de awk, la variable ENVIRON.

Esta variable posee una característica que no hemos visto todavía en ninguna variable. Se trata de una variable que contiene, a su vez, variables. A este tipo de variables se las denomina técnicamente matriz (array, en inglés). En concreto, ENVIRON contiene todas las variables del entorno actual, de ahí su nombre, abreviatura de environment (entorno).

Por ejemplo, la siguiente expresión dentro de awk:

ENVIRON["LANG"]

devolvería el valor actual de nuestra variable de entorno LANG.

Por su parte, la expresión:

ENVIRON["PATH"]

devolvería el valor actual de nuestra variable de entorno PATH.

En general, para obtener desde awk el valor de una variable del entorno, se debe usar la expresión:

ENVIRON["variable_de_entorno"]

donde variable_de_entorno es el nombre de una variable del entorno. (Nótense los corchetes ---típicos cuando se accede a variables de una matriz--- y las comillas. Ambos son obligatorios).

Parecería, pues, que tenemos la solución a nuestro problema en la punta de los dedos:

awk 'BEGIN {FS="\n"; print ENVIRON["$1"] ":"} { print " " $1 }'

Desgraciadamente no funcionará. Y no lo hará, porque la variable $1 ---y, en general, cualquier parámetro posicional---, aun siendo una variable de HAL, no es una variable del entorno.

¡Es desesperante. La guerra que está dando el dichoso detallito!

Tranquilidad. Estamos muy cerca de ganar la batalla.

Pensemos con atención el problema y reconstruyamos lo que acabamos de decir. Necesitamos una forma de pasar el valor de la variable $1 a awk. awk puede recibir el valor de variables ajenas a él cuando ese valor es el valor de una variable de entorno. Si $1 fuera una variable de entorno el problema estaría resuelto, pero ningún parámetro posicional es una variable de entorno. Ergo ... ¿Llegamos a la conclusión? ¿Y si consiguiésemos crear nosotros mismos una variable de entorno nueva? En tal caso, podríamos pasarle a ella el valor de $1 para que awk obtuviese dicho valor a través de la nueva variable de entorno. ¿Pero podemos hacer tal cosa, crear nosotros mismos variables de entorno?

Por supuesto que podemos crear variables de entorno. El proceso consta de dos pasos:

  1. Crear la variable propiamente dicha

  2. Hacer que esta nueva variable se convierta en variable del entorno

Para crear una variable cualquiera, basta con esta instrucción:

NOMBRE_DE_LA_VARIABLE=valor_de_la_variable

Por ejemplo, podemos crear la variable AMIGO:

AMIGO="HAL9000"

Para ver su valor, basta, como sabemos, con un simple echo:

echo $AMIGO

El segundo paso es pedir a HAL que haga de esta variable una variable de entorno, lo que técnicamente se llama exportar la variable:

export AMIGO

Podemos comprobar que la variable forma parte del entorno con printenv:

printenv AMIGO

Por cierto, lo normal cuando se quiere crear una variable de entorno es crearla y exportarla en un solo paso:

export AMIGO="HAL9000"

Siguiendo este mismo procedimiento, es posible crear una variable de entorno dentro de nuestro guión y asignarle el valor de $1, digamos, ARTICULO. Reeditemos, pues, el guión para que su primera línea sea ahora ésta:

export ARTICULO=$1

Finalmente, y puesto que la variable ARTICULO formará parte del entorno antes de que awk ponga en marcha sus resortes, podemos pasarle su valor como hicimos como otras variables de entorno en ejemplos anteriores:

ENVIRON["ARTICULO"]

Por tanto, la instrucción awk de nuestro guión deberá modificarse de la siguiente manera:

awk 'BEGIN {FS="\n"; print ENVIRON["ARTICULO"] ":"} { print " " $1 }'

Finalmente, y puesto que el valor de $1 y el de ARTICULO son el mismo desde el comienzo del guión, resulta más homogéneo y estético dar a dict como argumento el valor de ARTICULO en lugar de $1. Y no se olviden las comillas, que deben estar ahora ahí por la razón que se explicó el día pasado:

dict -d jargon "$ARTICULO"

Nuestro guión completo, con los cambios anteriores, quedará, pues, así:

export ARTICULO=$1

dict -d jargon "$ARTICULO" \
| grep -E -o '[{][^[:punct:]]+}' \
| sort \
| awk 'BEGIN {FS="\n"; print ENVIRON["ARTICULO"] ":"} { print " " $1 }' \
| tee -a jargon_refs

Para cerciorarnos de que el problema ha sido resuelto, volvemos a ejecutar la orden:

./buscar_en_jargon "hacker ethic"

El resultado carece del desliz por el casi nos ganamos un capón el día pasado y por cuya solución hoy mereceríamos una felicitación de HAL:

hacker ethic:
{FidoNet}
{GNU}
{gray hat}
{samurai}
{superuser}
{tiger team}
{Usenet}

Pero no vamos a conformarnos tan fácilmente. Queremos ir a por nota.

Se habrá advertido tanto en la nueva forma que acabó adoptando nuestro guión buscar_en_jargon como en alguno de los guiones construidos en sesiones anteriores que un guión puede constar de órdenes diferentes y que, puesto que HAL ejecutará secuencialmente cada línea del guión (cada orden), ya no es necesario utilizar el signo '\' más que cuando la línea del caso sea muy larga y convenga fragmentarla por razones de legibilidad, aunque para HAL seguirá siendo una única línea. De hecho, hasta ahora hemos estado utilizado líneas en blanco (que HAL pasará por alto dentro de un guión) para separar unas instrucciones de otras.

Puesto que podemos incluir más órdenes en nuestro guión, quizá podamos sacar más provecho de él. Podemos replantear el problema inicial ---siguiendo la máxima M7 formulada aquí--- y modificarlo ligeramente para obtener un mayor beneficio del esfuerzo realizado:

Mostrar la definición de un artículo del Jargon File, así como la lista de sus referencias cruzadas y guardar estas últimas en el fichero jargon_refs.

La idea nueva en este replanteamiento del problema es que queremos que se muestre también en pantalla el contenido mismo del artículo y no sólo sus referencias.

Conseguirlo es fácil. Veámoslo.

Sabemos que el contenido del artículo lo devuelve la orden dict. Pero en nuestro guión actual es redirigido mediante una tubería a grep para que proceda a analizarlo como se le indica, de ahí que no aparezca en pantalla:

dict -d jargon "$ARTICULO" \
| grep -E -o '[{][^[:punct:]]+}'

Una solución sencilla es hacer que la salida de dict sea redirigida, no a grep, sino a la pantalla y también al fichero temporal jargon_tmp via tee y hacer, luego, que grep, ya fuera de la tubería, lea el contenido de ese fichero. Finalmente, y puesto que no vamos a reutilizar el fichero temporal creado, conviene borrarlo una vez se haya ejecutado el resto de instrucciones. El esquema de este procedimiento sería el siguiente:

dict ... | tee jargon_tmp
grep ... jargon_tmp
...
rm jargon_tmp

Rellenemos lo que falta e incluyámoslo en el guión:

export ARTICULO=$1

dict -d jargon "$ARTICULO" | tee jargon_tmp

grep -E -o '[{][^[:punct:]]+}' jargon_tmp \
| sort \
| awk 'BEGIN {FS="\n"; print ENVIRON["ARTICULO"] ":"} { print " " $1 }' \
| tee -a jargon_refs

rm jargon_tmp

Probemos la nueva versión:

./buscar_en_jargon console

El resultado es para dar saltos de alegría:

1 definition found

From Jargon File (4.3.1, 29 Jun 2001) [jargon]:

console n. 1. The operator's station of a {mainframe}. In times past,
this was a privileged location that conveyed godlike powers to anyone
with fingers on its keys. Under Unix and other modern timesharing OSes,
such privileges are guarded by passwords instead, and the console is
just the {tty} the system was booted from. Some of the mystique remains,
however, and it is traditional for sysadmins to post urgent messages to
all users from the console (on Unix, /dev/console). 2. On microcomputer
Unix boxes, the main screen and keyboard (as opposed to character-only
terminals talking to a serial port). Typically only the console can do
real graphics or run {X}.


console:
{mainframe}
{tty}
{X}

El uso de ficheros temporales dentro de guiones es muy frecuente. No obstante, conviene no abusar de ellos. Una buena recomendación es buscar soluciones que no incluyan ficheros temporales y sólo acudir a ellos cuando no se vea un camino natural y sencillo para hallar la solución.

Hemos logrado bastante con no demasiado trabajo. Merece la pena acabar refinando el guión para otorgarle su forma definitiva ---al menos, mientras no se nos ocurran nuevas mejoras.

Lo primero que podemos hacer es convertir todos los nombres de ficheros en variables iniciales (que no se exportarán y que sólo tendrán vigencia dentro del guión). Aquí, en este guión simple, este tipo de técnica no tiene ninguna incidencia real, pero es conveniente acostumbrase a evitar introducir en las propias órdenes nombres de ficheros y, en general, cualquier otro tipo de dato ajeno al sentido de la orden misma. Aparte de la cuestión estética, es mucho más fácil leer un guión en el que queda claro desde el principio el tipo de ficheros que se va a manejar. Por otra parte, los futuros cambios de esos nombres son mucho más sencillos de realizar si se encuentran situados en una parte visible del guión y no entremezclados con las órdenes propiamente dichas.

El guión reformado de acuerdo con esta recomendación quedaría así:

export ARTICULO=$1
F_TEMPORAL=jargon_tmp
F_REFERENCIAS=jargon_refs

dict -d jargon "$ARTICULO" | tee $F_TEMPORAL

grep -E -o '[{][^[:punct:]]+}' $F_TEMPORAL \
| sort \
| awk 'BEGIN {FS="\n"; print ENVIRON["ARTICULO"] ":"} { print " " $1 }' \
| tee -a $F_REFERENCIAS

rm $F_TEMPORAL

Finalmente, conviene añadir unos pocos comentarios explicativos que puedan servir al futuro lector ---y nosotros mismos podemos ser ese futuro lector, cuando dentro de unos meses olvidemos el qué y el porqué de nuestro hoy tan transparente guión. Por supuesto, a HAL ---sobra decirlo---, le importan un bledo los comentarios realizados por y para humanos, pero es que a HAL no se le olvidan ni se le ofuscan las cosas que le competen como a nosotros, pobres mortales.

## buscar_en_jargon - 20/02/09 - átopos
## Recibe como único argumento un artículo del 'Jargon File'
## y devuelve el contenido del artículo y sus referencias
## cruzadas. Estas últimas se almacenan en el fichero
## F_REFERENCIAS
export ARTICULO=$1 # Artículo del Jargon File
F_TEMPORAL=jargon_tmp # Fichero temporal
F_REFERENCIAS=jargon_refs # Referencias contenidas en ARTICULO

dict -d jargon "$ARTICULO" | tee $F_TEMPORAL

grep -E -o '[{][^[:punct:]]+}' $F_TEMPORAL \
| sort \
| awk 'BEGIN {FS="\n"; print ENVIRON["ARTICULO"] ":"} { print " " $1 }' \
| tee -a $F_REFERENCIAS

rm $F_TEMPORAL


Tras esta larguísima conversación, podemos ir a la cama, tanto HAL como nosotros, y dormir a pierna suelta, que ya es hora.


Resumen:

  • awk puede acceder a las variables del entrono mediante un variable interna de tipo array, denominada ENVIRON. Por ejemplo, con ENVIRON["PATH"] accedemos desde awk a la variable PATH del entorno.

  • Crear variables es un procedimiento habitual en cualquier tipo de guión. Una variable se crea asignando un nombre a un valor. Por ejemplo, para crear la variable AMIGO y darle el valor "HAL9000" se utiliza la expresión AMIGO="HAL9000".

  • La orden export permite convertir una variable en variable del entorno. La creación y exportación de la variable se suelen realizar en un único paso. Por ejemplo, export AMIGO="HAL9000", crea la variable AMIGO, le asigna un valor y la exporta al entorno.

  • En un guión puede haber varias órdenes distintas. HAL las leerá secuencialmente al interpretarlo.

  • El uso de ficheros temporales es frecuente dentro de los guiones. Estos ficheros suelen eliminarse una vez han cumplido su objetivo.

  • Todo dato externo a la lógica de una orden suele asignarse como valor a variables que se declaran y definen al principio del guión. Los nombres de ficheros son un ejemplo típico.

  • Es recomendable añadir breves comentarios a un guión, con el fin de que facilitar su comprensión a futuros lectores y a nosotros mismos.

miércoles, 18 de febrero de 2009

Sutilezas de HAL

Sigamos haciendo pruebas con nuestro guión del día pasado.

Ahora, en lugar de pedirle a HAL que busque un término que consta de una sola palabra, le vamos a proporcionar uno que consta de dos:

./buscar_en_jargon "hacker ethic"

Si el lector ejecuta la orden anterior, sufrirá una temporal decepción, porque en lugar de ver el contenido del artículo hacker ethic, verá otra vez el del artículo hacker y una línea final desasosegante:

No definitions found for "ethic"

¿Cómo se explica este comportamiento? Contemplémoslo desde la perspectiva de HAL:

  1. Al llegar a su análisis de los argumentos de la orden, HAL se topó con unas comillas iniciales y guardó todo lo que fue encontrando por el camino en la variable $1 hasta alcanzar las comillas de cierre. O sea, que sí que reconoció las comillas. De hecho, si, en lugar de "hacker ethic" (con comillas) hubiésemos puesto hacker ethic (sin comillas), HAL habría guardado hacker en la variable $1 y ethic en la $2, es decir, habría tomado cada palabra por un argumento diferente. Pero sucedió también algo que no esperábamos ---y éste es el pero por el que la cosa falla---: lo que guardó HAL lo guardó sin las comillas, porque una vez que comprendió, gracias a nuestras comillas, que había un único argumento y no dos, se desentendió totalmente de ellas y se limitó a guardar en la variable $1 lo entrecomillado, es decir, hacker ethic, en lugar de "hacker ethic".

  2. Antes de ejecutar la primera línea de nuestro guión, HAL sustituyó la variable $1 por su valor (hacker ethic) y procedió, después, a ejecutarla ---las demás líneas, se recordará, estaban comentadas y, por tanto, eran invisibles para HAL.

  3. dict, por su parte, obró como debe hacerlo: nos devolvió el contenido del artículo hacker (su primer argumento) y un mensaje de error, puesto que no encontró ninguna entrada en el Jargon para el artículo ethic (su segundo argumento).

Está claro, pues, que debemos descubrir una forma de conseguir que el valor de $1 resulte entrecomillado antes de que HAL trate de ejecutar la orden contenida en el guión, de modo que, al sustituir la variable por su valor, HAL ejecute:

dict -d jargon "hacker ethic"

en lugar de lo que hizo antes:

dict -d jargon hacker ethic

Para este tipo de situaciones existe una triquiñuela sencilla, porque, una vez que se conoce el comportamiento descrito, es lo primero que a uno se le ocurre, a saber, entrecomillar el parámetro posicional:

"$1"

Gracias a esta argucia, podemos reeditar el guión una vez más y hacer que su primera línea sea ahora:

dict -d jargon "$1"

Hagamos un segundo intento.

./buscar_en_jargon "hacker ethic"

El lector comprobará que funciona a las mil maravillas, ya sin sustos de ninguna clase.

Bien, verificada la corrección de la primera línea de nuestro guión, toca descomentar las líneas anteriormente comentadas y volver a ejecutarlo. Dicho y hecho, nuestro guión debe ser ahora el siguiente:

dict -d jargon "$1" \
| grep -E -o '[{][^[:punct:]]+}' \
| sort \
| awk 'BEGIN {FS="\n"; print "hacker:"} { print " " $1 }' \
| tee -a jargon_refs

Probemos su funcionamiento completo:

./buscar_en_jargon "hacker ethic"

El resultado será:

hacker:
{FidoNet}
{GNU}
{gray hat}
{samurai}
{superuser}
{tiger team}
{Usenet}

Casi perfecto. Pero lamentablemente hay un fallito. En lugar de la palabra hacker, el título de la serie de referencias cruzadas debería ser hacker ethic. En esta ocasión el fallo no es, en modo alguno, achacable a HAL. Ya podemos dar gracias de que HAL no tenga nudillos y nos propine un capón por lo despistados que somos. Porque, claro ---ahora lo vemos---, se nos olvidó cambiar la cadena de caracteres que empotramos en el primer print de la orden awk dentro del guión, una rémora de nuestra prehistórica versión inicial:

awk 'BEGIN {FS="\n"; print "hacker:"} { print " " $1 }'

Por supuesto, en lugar de esa cadena tiene que haber también una variable, algo que luego HAL, cuando interprete el guión, sustituya por el primer argumento de nuestra orden ./buscar_en_jargon.

Y aquí viene el escollo. Sucede a menudo. Tenemos todo a punto menos un pequeño detalle, que es el que más difícil resulta de arreglar. ¡Vaya mala pata! Pero la paciencia es la madre de la ciencia y algo nuevo aprenderemos si deshacemos el actual embrollo. Que no es fácil de deshacer, resulta obvio a poco que se piense. No funcionaría en absoluto algo como esto:

awk 'BEGIN {FS="\n"; print $1 ":"} { print " " $1 }'

Y no por las comillas, sino por algo mucho más inmediato: awk usa ese mismo nombre de variable para una variable interna suya, a saber, la que contiene el valor del primer campo de cada registro del flujo de texto entrante, como se explicó hace algún tiempo. Es decir, dentro de awk la variable $1 tiene un significado diferente que el que esa misma variable tiene para HAL fuera de la instrucción awk.

Estaríamos en un callejón sin salida si awk no nos proporcionase un medio para poder acceder al valor de una variable ajena a las suyas propias. Por fortuna ---o más bien, por obra de sus concienzudos creadores---, awk dispone de un mecanismo que nos viene de perillas para salir del atolladero. Lo veremos el día que viene.


Resumen:

  • Un parámetro posicional pensado para contener un argumento que consta de varias palabras de una orden emitida desde la línea de órdenes se debe entrecomillar.

  • Una misma variable puede tener significados distintos dependiendo del contexto en que se encuentre. Normalmente, prevalece el significado del contexto más próximo.

Más variables para HAL

El día pasado creamos nuestro primer guión y convertimos en algo fácil de recordar y ejecutar una orden compleja que resultó ardua de escribir y que sería insoportable tener que volver a reproducir cada vez que queremos realizar la tarea para la que fue diseñada.

Hoy vamos a seguir los mismos pasos con otra orden, también compleja, la que constituyó la coronación de nuestros esfuerzos en la primera parte de esta serie de conversaciones. Es recomendable que el lector relea por encima los dos últimos pasos de aquellos artículos, que se titularon "Condimentos al gusto de HAL" (I y II).

Recordemos que la orden compleja que entonces creamos fue ésta:

dict -d jargon hacker \
| grep -E -o '[{][^[:punct:]]+}' \
| sort \
| awk 'BEGIN {FS="\n"; print "hacker:"} { print " " $1 }' \
| tee -a jargon_refs

Su propósito era mostrar en pantalla todas las referencias cruzadas a artículos del Jargon File contenidas en el artículo hacker de ese mismo diccionario y guardarlas, además, en el fichero jargon_refs donde se almacenarían junto con otras series de referencias que hubiesen aparecido en anteriores consultas.

Entonces se planteo una objeción obvia, a la que no dimos respuesta: ¿por qué no generalizar el resultado?

Ahora, por fin, disponemos de un recurso para proceder a una generalización básica de la citada orden. Merece la pena prestar atención a la estrategia que vamos a seguir, porque abre por vez primera las puertas hacia una conversación realmente inteligente con nuestro querido HAL.

Lo primero es crear, como el día pasado, un guión con esa orden, digamos, buscar_en_jargon. Nos movemos al directorio guiones. Abrimos el editor, como de costumbre, dándole como argumento el nombre de fichero indicado. Copiamos la orden y guardamos la edición. Podemos ahora salir del editor o, mejor, enviarlo al trasfondo con Ctrl+Z, porque vamos a tener que reeditar el guión en breve. Finalmente cambiamos el modo del fichero para que al menos nosotros podamos ejecutarlo... Está bien, recordaremos una vez más cómo se hace, pero a ver si es la última:

chmod u+x buscar_en_jargon

Si ahora le pedimos amablemente a HAL que interprete nuestro nuevo guión:

./buscar_en_jargon

nos mostrará exactamente el mismo resultado que obtuvimos antaño, o sea, la lista de todas las referencias cruzadas del término hacker en el Jargon File.

No estamos hoy en ninguna fecha especial, salvo por el hecho de que hace dos días ha salido de la fábrica la tan esperada nueva versión estable de Debian y muchos usuarios puede que hayan actualizado hoy mismo la suya con un sencillo aptitude dist-upgrade.

Pero da igual el día, cualquier día es bueno para pedir deseos. Y qué mejor deseo que imaginar que HAL pudiera entender algo como esto:

./buscar_en_jargon distro

Es decir, que pudiésemos introducir un argumento para nuestra orden buscar_en_jargon, como hacemos con otras órdenes, y que pudiésemos así evitarnos el estúpido trabajo de tener que editar el fichero que la contiene cada vez que quisiésemos buscar en el Jargon una palabra distinta a hacker.

HAL puede cumplir nuestro deseo. Dispone de un medio para hacerlo. Ese medio es una cosa muy pequeña, una sencilla variable interna. Tiene esta humilde forma, que quizá no nos resulte del todo extraña:

$1

Veamos cómo funciona. Aunque se pueden poner ejemplos de su uso directamente desde la línea de órdenes, el funcionamiento de este parámetro posicional ---como se denomina técnicamente--- y de sus congéneres ($0, $2, $3, etc.) se entiende con mucha mayor naturalidad si creamos un guión.

Vamos a llamar a nuestro guión, saludo_tontorron. Consta de las siguientes líneas:

echo $0
echo $1
echo $2

Tras guardarlo y cambiarle el modo, lo ejecutamos de esta forma:

./saludo_tontorron hola amigos

El resultado será:

./saludo_tontorron
hola
amigos

¿Qué ha sucedido? Muy simple. HAL lee y ejecuta línea a línea nuestro saludo_tontorron. Cada línea consta de una instrucción echo que toma como argumento un parámetro posicional. Como sabemos de otro día, cuando el argumento de echo es una variable ---como los son $0, $1 y $2--- devuelve el valor de esa variable. El valor de la variable $0 es la orden ejecutada; el de la variable $1, el primer argumento; el de la $2, el segundo; etc.

Algo tan simple es extraordinariamente útil. Cada orden que ejecutamos es analizada miembro a miembro por HAL (donde cada miembro, como sabemos, está separado, por defecto, por un espacio). HAL guarda, además, temporalmente cada uno de los componentes de la orden en un parámetro posicional. De este modo, y mientras se ejecuta la orden, podremos reutilizar el valor de sus componentes.

La aplicación para nuestro caso es inmediata. En nuestro guión buscar_en_jargon, la primera línea era ésta:

dict -d jargon hacker

La orden la ejecutábamos luego sin argumentos:

./buscar_en_jargon

Al analizar HAL esta última orden, guardara el único miembro de la orden ./buscar_en_jargon en el parámetro posicional $0. Pero si al ejecutarla le añadiésemos un argumento, ese argumento se guardaría en el parámetro posicional $1. O sea, que si nuestra orden hubiese sido:

./buscar_en_jargon hacker

la palabra hacker se habría guardado en el parámetro $1. Y, si en lugar de hacker, hubiésemos puesto cualquier otro término, sería este otro término el que habría quedado almacenado en $1.

Procedemos, pues, a reeditar nuestro guión de tal forma que en lugar del término concreto hacker aparezca $1, cuyo valor será, en consecuencia, sustituido, por el primer argumento que demos a nuestro guión al ejecutarlo. Comentamos también el resto de líneas, que HAL dejará de ver, para poder ir probando paso a paso nuestra nueva y maravillosa adquisición. El guión remozado quedaría así:

dict -d jargon $1 \
#| grep -E -o '[{][^[:punct:]]+}' \
#| sort \
#| awk 'BEGIN {FS="\n"; print "hacker:"} { print " " $1 }' \
#| tee -a jargon_refs

Le toca al lector disfrutar a lo grande con el resultado del cambio, por ejemplo:

./buscar_en_jargon hacker

O, por qué no:

./buscar_en_jargon Linux


Resumen

  • HAL analiza pieza a pieza las órdenes que se ejecutan desde la línea de órdenes y guarda temporalmente el contenido de cada componente de dicha orden en variables internas denominadas parámetros posicionales.

  • Los parámetros posicionales son $0, $1, etc., donde el valor de $0 es la orden ejecutada en la línea de órdenes; el de $1, el de su primer argumento; etc.

lunes, 16 de febrero de 2009

Mi primera orden para HAL

Es hora ya de agarrar fuertemente el timón y enderezarlo para que apunte al objetivo inicialmente marcado:

... escribir nuestras órdenes complejas en ficheros de texto y hacer que HAL las lea de ahí y las interprete como debe.

Conocemos todos los pasos del proceso por los cuatro últimos artículos de esta serie de conversaciones. Vamos, no obstante, a reproducirlos, pero esta vez con un ejemplo de verdad. Para ello, en lugar de hacer que HAL lea un tonto fichero de prueba y ejecute su contenido, utilizaremos el resultado de una de las tareas que el lector debería haber completado. Al fichero que guardará las órdenes necesarias para llevar a buen término la susodicha tarea lo llamaremos chuleta_cch, que será, por tanto, el nombre de nuestro guión y el nombre de la orden que HAL ejecutará, como cualquier otra orden del sistema, tantas veces como queramos.

Los pasos son los siguientes:

  1. Moverse al directorio guiones, dentro de nuestro home:

    cd guiones

  2. Editar el fichero chuleta_cch con el siguiente contenido [una de las posibles soluciones al problema complementario planteado aquí]:

    wget \
    http://los-pajaros-de-hogano.blogspot.com/feeds/posts/default/-/Conversaciones\ con\ HAL?max-results=50

    mv Conversaciones\ con\ HAL\?max-results\=50 Conversaciones\ con\ HAL

    LC_MESSAGES="C" whatis \
    $(grep -E -o '<code class="orden">[[:alnum:]]*</code>' \
    Conversaciones\ con\ HAL \
    | sed -e 's/<code class="orden">//g' -e 's/<\/code>//g' \
    | sort -u) \
    | grep -E '\(1\)|\(8\)' \
    | sed -e 's/(1)//' -e 's/(8)//g' \
    | tee chuleta.txt

    more chuleta.txt

  3. Tras guardar los cambios en el fichero, modificar su modo para que contenga el necesario permiso de ejecución:

    chmod u+x chuleta_cch

  4. Pedir a HAL que lo ejecute:

    ./chuleta_cch


El resultado es la tan ansiada chuleta de las órdenes aprendidas hasta hoy en nuestras conversaciones, que habrá quedado guardada, además, en el fichero chuleta.txt:

apropos - search the manual page names and descriptions
aptitude - high-level interface to the package manager
awk - pattern scanning and text processing language
bc - An arbitrary precision calculator language
chmod - change file mode bits
cp - copy files and directories
cut - remove sections from each line of files
date - print or set the system date and time
dict - DICT Protocol Client
echo - display a line of text
file - determine file type
grep - print lines matching a pattern
locale - Get locale-specific information.
ls - list directory contents
man - an interface to the on-line reference manuals
mkdir - make directories
more - file perusal filter for crt viewing
mv - move (rename) files
printenv - print all or part of environment
readelf - Displays information about ELF files.
rm - remove files or directories
sed - stream editor for filtering and transforming text
sort - sort lines of text files
strings - print the strings of printable characters in files.
su - change user ID or become superuser
tee - read from standard input and write to standard output and files
tty - print the file name of the terminal connected to standard input
whatis - display manual page descriptions
zmore - file perusal filter for crt viewing of compressed text


La orden chuleta_cch es la primera orden que hemos creado nosotros mismos y, en cuanto tal, podremos ejecutarla siempre que deseemos actualizar la chuleta de las órdenes tratadas en estas conversaciones. Una buena recompensa para el principiante que haya llegado hasta aquí y una demostración incontrovertible de las infinitas posibilidades y de la versatilidad que HAL pone generosamente en nuestras manos.

domingo, 15 de febrero de 2009

Con tu permiso, HAL

Expresemos parte de lo visto en los dos artículos precedentes en forma de un diálogo real, al modo como lo hicimos hace algún tiempo, pero esta vez incluidas también "las reflexiones" del propio HAL. Aparte de ser un experimento divertido, allanará definitivamente el camino hacia la siguiente y muy importante estación. [Omitimos otras muchas reflexiones que hace HAL y que todavía no han sido explicadas y nos limitamos sólo a aquellas que atañen al tema actual.]

YO:
Acabo de crear el fichero 'prueba' en mi directorio 'guiones', en el que precisamente estoy en este momento, y quiero que HAL lo ejecute como si se tratase de una orden más.

prueba


HAL:
Veo en la línea de órdenes el nombre 'prueba'. Puesto que es la primera ---y aquí única--- palabra en la línea de órdenes, se trata del nombre de un fichero ejecutable. Como este nombre no es la ruta completa de un fichero ejecutable, debo construir yo mismo la ruta completa a partir de la información contenida en la variable 'PATH' que es donde están definidas las rutas completas de los directorios que contienen ficheros ejecutables. Añadiré, pues, el nombre de fichero 'prueba' a la ruta de cada directorio definido en 'PATH'.

El primer directorio en 'PATH' es '/usr/local/bin'. Por tanto, la ruta completa del fichero que me han dado es '/usr/local/bin/prueba'. No encuentro este fichero. Repetiré el proceso para el siguiente directorio en 'PATH'.


[HAL repite el mismo paso para los demás directorios: '/usr/bin', '/bin', '/usr/bin/X11' y '/usr/games' y obtiene el mismo resultado: fichero no encontrado]

El fichero no se ha encontrado. Tengo una respuesta preparada para esta situación, que imprimo en la salida

bash: prueba: command not found


YO:
La orden no se ha encontrado porque mi directorio '/home/atopos/guiones' no está incluido en la variable 'PATH'. En lugar de cambiar la variable voy a dar a HAL la ruta completa de mi fichero 'prueba' por medio de una abreviatura:

./prueba


HAL:
Veo en la línea de órdenes la expresión './prueba'. El primer signo es una abreviatura del directorio actual de trabajo. Lo primero que debo hacer es expandir la abreviatura. Esto me da como resultado:

/home/atopos/guiones/prueba

Puesto que ésta es la primera ---y aquí única--- expresión en la línea de órdenes, se trata del nombre de un fichero ejecutable.

Antes de ejecutarla, compruebo los permisos de ejecución del fichero. El usuario atopos no tiene permisos para ejecutar el fichero. Tengo una respuesta preparada para esa situación, que imprimo en la salida:


bash: /home/atopos/guiones/prueba: Permiso denegado


YO:
Increíble, pero cierto. Incluso estando en nuestro 'home' particular, HAL nos dice que no tenemos permisos. ¡Esto sí que es un galimatías!


La reflexión de HAL que precede a su desconcertante respuesta sobre los permisos alude un aspecto crucial en el que hay que detenerse.

Ya vimos al comienzo de estas conversaciones que HAL es un sistema multiusuario. También sabemos que al menos dos usuarios han de convivir en HAL, nosotros y el superusuario, y que no todos los usuarios tiene los mismos privilegios en el sistema, como se deduce del hecho de que el superusuario tenga todos y nosotros sólo algunos.

Pero la cosa es algo más compleja y sofisticada que la mera distinción entre un usuario superpoderoso y el resto de los mortales. Y lo es en aras de nuestra seguridad y de la del sistema en conjunto. Veamos en qué consiste básicamente esta complejidad. Lo mínimo necesario como para poder entender la anterior y desconcertante respuesta de HAL.

En primer lugar, todo fichero que reside en HAL ---también todo directorio y toda instancia de un programa en ejecución, pero nosotros nos limitaremos sólo a hablar de ficheros, que es lo que necesitamos ahora--- se considera propiedad de un usuario del sistema y de un grupo de usuarios del sistema, y, téngase en cuenta, que todo usuario, a su vez, es siempre miembro de uno o varios grupos. Los usuarios que ---mala suerte--- no son propietarios de un fichero ni pertenecen a un grupo que tenga derechos de propiedad sobre ese fichero son, en relación con ese fichero, unos don nadie y se los denomina los otros en general.

Sobre esta división horizontal del inmueble en que consiste el sistema de ficheros de HAL, se cruza otra caracterización, relativa a las acciones que se pueden realizar con dichos ficheros, y que son, básicamente, tres: leer, escribir y ejecutar un fichero.

Estas dos categorizaciones se entreveran, como decimos, y forman una trama bidimensional en torno a todos los ficheros del sistema. Dicho de otra forma, sobre cada fichero se puede construir una tabla donde se relacionan propietarios y acciones. Por ejemplo, para nuestro fichero prueba podríamos construir una tabla del siguiente tipo:

Fichero prueba [propietarios-acciones]
/-----------------------------------------------------\
| | leer | escribir | ejecutar |
|-----------------------------------------------------|
| usuario | | | |
|-----------------------------------------------------|
| grupo | | | |
|-----------------------------------------------------|
| otros | | | |
\-----------------------------------------------------/

Mediante esta tabla podemos establecer un mecanismo de control fino sobre lo que cada propietario ---o don nadie--- puede hacer con un fichero en el sistema, establecer los permisos que dichos usuarios tienen o no tienen en relación con ese fichero. El conjunto de permisos de un fichero de HAL, organizados de la forma indicada, se denomina el modo de ese fichero.

Consideremos, por ejemplo, nuestro fichero prueba. Su usuario propietario es, en mi caso, atopos ---en el del lector, será su nombre de usuario. El usuario atopos pertenece, a su vez, a un grupo con el mismo nombre, atopos, el cual también tiene derechos de propiedad sobre ese fichero. Pues bien, el modo de este fichero, esto es, la secuencia de permisos correspondientes a todos sus posibles usuarios, es actualmente el siguiente:

Fichero prueba [permisos = modo]
/------------------------------------------------------\
| | leer | escribir | ejecutar |
|------------------------------------------------------|
| U:atopos | Permitido | Permitido | No permitido |
|------------------------------------------------------|
| G:atopos | Permitido | No permitido | No permitido |
|------------------------------------------------------|
| otros | Permitido | No permitido | No permitido |
\------------------------------------------------------/

Esta tabla se puede escribir también de esta otra forma (donde 1 está por "Permitido" y 0 por "No permitido"):

/--------------------------------------------------------------------------------------\
| usuario:atopos | grupo:atopos | otros |
|--------------------------------------------------------------------------------------|
| leer | escribir | ejecutar | leer | escribir | ejecutar | leer | escribir | ejecutar |
|--------------------------------------------------------------------------------------|
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
\--------------------------------------------------------------------------------------/

O, también de esta otra, si en lugar de los términos "leer", "escribir" y "ejecutar", ponemos una letra de sus equivalencias en inglés (read, write y execute):

/-----------------------------------\
| U:atopos | G:atopos | otros |
|-----------------------------------|
| r | w | x | r | w | x | r | w | x |
|-----------------------------------|
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
\-----------------------------------/

Todavía podemos abreviar la tabla canónica anterior de dos formas distintas.

La primera consiste en suprimir la última fila y mantener la letra de la penúltima allí donde su valor sea 1 (= "Permitido") o sustituirla por un guión donde su valor sea 0 (= "No permitido"):

/-----------------------------------\
| U:atopos | G:atopos | otros |
|-----------------------------------|
| r | w | - | r | - | - | r | - | - |
\-----------------------------------/

La segunda forma de abreviar la tabla canónica en cuestión es suprimir directamente la fila intermedia, o sea:

/-----------------------------------\
| U:atopos | G:atopos | otros |
|-----------------------------------|
| 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
\-----------------------------------/

Todo esto está muy bien ---dirá el lector---, pero no se ha explicado todavía cómo ha llegado el autor a adivinar el modo del fichero prueba, es decir, la secuencia de permisos que lo caracteriza.

No se trata de una adivinanza. El modo de cualquier fichero se puede conocer perfectamente con una orden familiar, la orden ls, la que sirve para listar el contenido de un directorio. Existe una opción de ls que nos da una información detallada de ese contenido. Se trata de la opción -l ('L' minúscula, abreviatura del inglés long). Es bueno saber también que, aunque ls, sin argumentos, lista el contenido completo del directorio en que se ejecuta, puede recibir un fichero concreto como argumento. Veamos qué nos dice HAL si le pedimos que nos dé una información detallada sobre el fichero prueba, que, como sabemos, está en nuestro directorio guiones, al cual debe cambiar el lector, si no lo ha hecho todavía, con cd guiones:

ls -l prueba

La respuesta es la siguiente:

-rw-r--r-- 1 atopos atopos 4 feb 13 01:38 prueba

No es momento de entender cada elemento de esta respuesta. Basta con atender ahora a las siguientes columnas subrayadas:

-rw-r--r-- 1 atopos atopos 4 feb 13 01:38 prueba
========= ====== ======
MODO U G

El modo rw-r--r-- es exactamente el que apareció antes en forma tabular, salvo que se sobrentiende el orden de los tipos de usuario: los tres primeros corresponden al usuario propietario del fichero, los tres siguientes al grupo propietario del fichero y los tres últimos a los otros, que no son propietarios. (El guión que precede al modo no debe confundir al lector, se trata simplemente de una indicación de que el fichero es justo eso, un fichero normal y corriente y no, por ejemplo, un directorio, en cuyo caso ese guión sería sustituido por la letra 'd'). Las otras dos columnas obviamente indican el nombre del usuario y grupo, respectivamente, que son propietarios del fichero.

Podemos hacer una prueba más con la orden ls -l, por ejemplo, darle como argumento el fichero /etc/locale.gen al que nos referimos en una de nuestras primeras conversaciones:

ls -l /etc/locale.gen

Que devuelve un modo de fichero idéntico, aunque el usuario y el grupo son ahora distintos:

-rw-r--r-- 1 root root 8231 ene 21 13:29 /etc/locale.gen
========= ==== ====


Regresemos al caso de nuestro fichero prueba. Hemos visto que nosotros somos sus usuarios propietarios y que, de entre todos los usuarios del sistema, somos los que gozamos de más privilegios sobre él (derechos de lectura y escritura, frente a los de solo lectura, que tienen otros usuarios del grupo de mi mismo nombre y el resto de usuarios, que, en consecuencia, no podrán modificar el fichero de forma alguna). Esto es algo perfectamente natural, puesto que somos nosotros mismos los creadores del fichero, y sólo a nosotros nos debe estar permitido modificar lo que es patrimonio de nuestra inventiva.

Que el citado fichero y, en general, todos los demás ficheros que creemos en nuestro home adquieran de antemano esta definición de privilegios, este modo, es algo que hace HAL por nosotros. Podríamos, si lo quisiésemos, modificar este comportamiento de HAL, por ejemplo, hacer que todos los ficheros que creásemos activasen el permiso de ejecución para nosotros mismos. Pero no sería, en absoluto, conveniente. ¿Por qué? En primer lugar, porque un fichero ejecutable es, en principio, un caso excepcional en nuestro home. La gran mayoría de nuestros ficheros no van a ser órdenes que deba ejecutar HAL. Una imagen, un fichero de audio, un fichero que albergue nuestra última novela no son ficheros que contienen instrucciones ejecutables. En segundo lugar, y ésta es una razón aún más importante, no es razonable activar la posibilidad de ejecución para todo fichero. Quién sabe lo que un usuario malintencionado podría hacer si descubre que todo fichero de nuestro home es potencialmente ejecutable.

Puesto que el modo que, por defecto, asigna HAL a todos nuestros ficheros es el descrito, esto es, rw-r--r--, cuando creemos un fichero ejecutable tendremos, además, que modificar estos permisos, cambiar su modo (change mode), para que adopte al menos esta forma: rwxr--r--, es decir, para que al menos nosotros mismos, sus propietarios, podamos ejecutarlo.

Pidámosle, pues, a HAL que cambie el modo de nuestro fichero prueba:

chmod u+x prueba

Podemos comprobar el resultado:

ls -l prueba

Veremos esto:

-rwxr--r-- 1 atopos atopos 4 feb 13 01:38 prueba
=========

La orden chmod, que es, como vemos, la que permite cambiar el modo de un fichero, toma como primer argumento el nuevo modo del fichero. En el caso del ejemplo este nuevo modo viene expresado por una representación simbólica del cambio que se quiere realizar. El formato general de esta representación simbólica consta de tres partes:

tipo(s)-de-usuario(s)[+/-]acción

Aquí, los tipo(s)-de-usuario(s) son u, para usuario propietario; g, para el grupo propietario; o para otros. Los signos '+' y '- dan o quitan, respectivamente, el permiso al tipo de usuario especificado en la primera parte de la expresión. Finalmente, la última parte de la expresión establece la acción ---lectura (r), escritura (w) o ejecución (x)--- para la que se concede o deniega el permiso.

Así, en nuestro caso, u+x significa que queremos que el nuevo modo conceda (+) el permiso de ejecutar el fichero (x) al usuario propietario del mismo (u). Si quisiésemos conceder el permiso de ejecución a todos los tipos de usuarios, pondríamos ugo+x, que abreviadamente se puede escribir también a+x, donde a es una abreviatura de all (todos, en inglés). Si, por el contrario, quisiésemos denegar el permiso de lectura a todos los usuarios o grupos menos el propietario del fichero, la expresión sería go-r.

Existe otra forma de indicar el modo, que para algunos usuarios con predisposición matemática resulta más cómoda o intuitiva. Se trata de indicarla numéricamente, utilizando la cadena de dígitos que vimos antes. Según esta cadena, el modo de nuestro fichero prueba antes del cambio es: 110100100. Si queremos activar el permiso de ejecución para el usuario propietario, el nuevo modo sería 111100100. Ahora bien, en lugar de utilizar esta representación binaria (en base 2) se usa y debe usarse su equivalencia en octal (en base 8). 110100100 equivale en octal a 644, mientras que el nuevo modo, 111100100, equivale en octal a 744. Por tanto la anterior orden de cambio de modo se podría haber escrito de esta forma:

chmod 744 prueba

Animo al lector a que practique por su cuenta diversos cambios de modo con chmod, ya sea mediante la representación simbólica o la numérica. En este último caso, le puede venir bien echar mano de la clásica calculadora de HAL, bc, que se puede usar en modo interactivo o, mejor incluso, en una tubería. Por ejemplo, para obtener la representación en octal del número binario 111100100, podríamos utilizar la orden:

echo "obase=8; ibase=2; 111100100" | bc

donde obase indica la base numérica del dato de salida (output base) e ibase la del dato de entrada (input base).

Tras esta prolongada zambullida en el mundo de los permisos de ficheros de HAL y, una vez cambiado el modo del fichero prueba, que, como se recordará, contiene, sólo la línea pwd, podemos por fin hacer que HAL lo ejecute como una orden, sin temor a que nos vuelva a negar el permiso:

./prueba

Ahora sí podemos respirar tranquilos, porque el resultado es exactamente el que debe ser:

/home/atopos/guiones


Resumen:

  • HAL dispone de un riguroso sistema de control de acceso a sus ficheros según el tipo de usuario que se sea. Hay tres tipos de usuario: usuario propietario del fichero, grupo de usuarios propietarios del fichero y el resto de usuarios, los otros, que no son propietarios del fichero. Cada uno de estos usuarios puede recibir o no el permiso de lectura, de escritura o de ejecución sobre un determinado fichero.

  • Por defecto, HAL asigna un modo de control de acceso, una secuencia de privilegios, a cada fichero.

  • La opción -l de la orden ls nos da una información detallada de los ficheros que se le den como argumentos o, si no se le dan argumentos, de todos los ficheros del directorio desde la que se ejecuta. Esta información detallada incluye el modo del fichero y el nombre de sus propietarios.

  • La orden chmod permite cambiar el modo de un fichero. Por ejemplo, chmod u+x mi-orden añade el permiso de ejecución para el usuario propietario del fichero mi-orden.

  • bc es la clásica calculadora de HAL para la consola.

HAL y la senda

Del artículo anterior podemos extraer una simple idea con la que seguir adelante. Cuando escribimos un fichero ---como nuestro fichero prueba--- con la intención de que HAL lea de él las órdenes que contiene, estamos creando un fichero semejante a aquéllos en que consisten las órdenes que HAL nos proporciona de antemano, es decir, un fichero ejecutable (= una orden) y, particularmente, del mismo tipo que las órdenes escritas en el lenguaje interpretado que venimos denominando la "lengua de HAL".

Esta sola idea nos puede llevar a una conclusión casi completamente acertada, a saber, que para pedir a HAL que ejecute nuestro fichero prueba bastará con poner su nombre en la línea de órdenes.

Vamos a probarlo:

prueba

La respuesta es, desgraciadamente, ¡la misma de siempre!:

bash: prueba: command not found

¿Qué falla en nuestra deducción? Piense el lector unos segundos más y comprenderá que el hecho de que todas las demás órdenes conocidas se encuentren en directorios específicos (/bin, /sbin, etc.) puede tener que ver con el desesperante resultado.

Recordemos, de entrada, una de las conclusiones con las que se inició el artículo anterior y que reclama ahora una atención mayor:

... una de las primerísimas cosas que comprueba HAL cuando nos comunicamos con él es si la palabra introducida en la línea de órdenes corresponde o no al nombre de una orden existente en el sistema.

Nuestra palabra prueba, esto es, el nombre de fichero prueba, fue sometido a esta inicial verificación y descartado como orden porque la ruta del directorio que lo contiene no es ninguna de aquéllas en las que HAL entiende que deben estar los ficheros ejecutables, las órdenes. Y HAL, el muy pillín, conoce de antemano esas rutas, antes incluso de que iniciemos una conversación con él. ¿Cómo lo conoce? No es ningún misterio. Del mismo modo que obtiene sus otros conocimientos iniciales, por medio de una variable de entorno.

Recordaremos seguramente el tema de las variables de entorno en relación con nuestro esfuerzo por hacer que HAL respondiera en el idioma que deseábamos. Allí quedó determinado su sentido:

... desde el mismo momento que iniciamos nuestra charla con HAL, hay una serie de aspectos relativos a ella que han quedado establecidos de antemano y sin que nosotros hayamos sido conscientes. Los arquitectos de nuestra distribución son los responsables de haberlos definido por nosotros para nuestro propio beneficio, y el de HAL. Estos aspectos se denominan variables de entorno, porque son aspectos modificables del entorno de diálogo en que nos encontramos de entrada.

Pues bien, la variable de entorno donde se especifican las rutas de los ficheros ejecutables es la variable PATH (ruta o senda, en inglés). Podemos conocer su valor actual de varias formas. Por ejemplo, mediante la orden printenv, que, como su nombre indica (abreviatura de print environment) imprime en la salida el entorno actual. Por defecto, printenv devuelve todas las variables y sus valores actuales. Si se especifican como argumentos variables determinadas, devuelve el valor de esas variables.

Consultemos a HAL sobre el valor actual de la variable PATH:

printenv PATH

La respuesta de mi HAL es ésta:

/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games

Se trata de una lista de todas las rutas de los directorios donde residen órdenes ejecutables y donde cada ruta está separada de la siguiente por el signo ':'.

Se verá que faltan las rutas donde, según el artículo anterior, residen los ejecutables del sistema: /sbin y /usr/sbin. La razón es que sólo el superusuario puede hacer ejecutar estas órdenes del sistema y puesto que no somos el superusuario ---a menos que nos pongamos el disfraz de Superman y nos convirtamos en él--- nuestro entorno de usuario corriente no contiene tales rutas. El lector debería comprobar que hacer ejecutar la orden anterior (printenv PATH) con el disfraz de superusuario le dará otra respuesta, donde sí están incluidas las rutas de los ejecutables del sistema y donde no aparecerán, probablemente, otras indignas de la atención de root:

/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

Hay otra forma diferente de conocer el valor de la variable PATH, que no requiere el uso de una nueva orden y que nos viene bien para aprender un nuevo elemento de la lengua de HAL.

Como sabemos, la orden echo repite tal cual los argumentos que recibe. Pero existen excepciones a esa función suya de devolver sin ton ni son lo mismo que le damos. Una de estas excepciones tiene lugar cuando el argumento de echo es una variable con el prefijo $. Veamos el porqué.

Sabemos lo que es una variable, porque ya nos hemos topado con ellas, de hecho estamos ahora mismo hablando de una en concreto. Las variables ---incluidas las variables de entorno--- no son más que nombres asociados a ciertas informaciones. Por ejemplo, la variable LANG es el nombre que recibe en el entorno la información relativa a la lengua del sistema (inglés, español, japonés, etc.). A la información actualmente asociada con ese nombre se la denomina técnicamente el valor actual de esa variable, un valor que podemos cambiar cuando queramos (de ahí lo de variable). En nuestro caso, el valor actual de LANG es el español, pero podemos cambiarlo al japonés cuando queramos. Pues bien ---y siguiendo con el ejemplo--- $LANG, con el prefijo del dolar delante del nombre de la variable, es la forma que en lengua de HAL sirve justamente para designar el valor de la variable LANG.

Ahora bien, la orden echo, que recibe como argumento cualquier fragmento de texto, puede recibir también perfectamente el tipo de fragmento que sirve para designar el valor de una variable, por ejemplo, el vocablo $LANG. Cuando echo se encuentra con un argumento de esta clase, deja por un momento de comportarse de la manera simplona que lo hace ---reproducir tal cual lo que ha recibido--- y nos devuelve el valor de la variable cuyo nombre sucede al signo del dolar. Así:

echo $LANG

devuelve:

es_ES.UTF-8

y no simplemente ---como podría predecirse sin la anterior consideración---:

$LANG

En general, podemos saber el valor de cualquier variable y también de la variable que nos interesa en este artículo, la variable PATH, mediante la orden echo:

echo $PATH

Que, como era de esperar, nos vuelve a mostrar la salida obtenida con printenv PATH:

/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games

Si volvemos al comienzo de la digresión, nos resultará evidente por qué nuestra orden prueba, cuya ruta completa ---conviene recordarlo--- es /home/[nombre-usuario]/guiones/prueba no ha sido aceptada por HAL como una orden. Simplemente, el directorio /home/[nombre-usuario]/guiones no forma parte de la variable PATH.

Es evidente, también, que para obligar a HAL a que acepte nuestra orden prueba como tal orden y, en general, todos los guiones que a partir de ahora escribamos para él no queda más remedio que modificar la variable PATH para que incluya la ruta del directorio /home/[nombre-usuario]/guiones.

Sabemos modificar variables de entorno, lo hicimos hace tiempo con las variables del entorno local [véanse los artículos El supermanual de HAL y HAL y mis ficheros]. Probemos, pues, a pedir a HAL que ejecute nuestra orden prueba, pero con el PATH previamente modificado, tal como hicimos en el primero de los artículos referidos:

PATH=/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/home/atopos/guiones \
prueba

Antes de pulsar Enter consideremos tres aspectos de esta orden. El primero es obvio: la última ruta incluida en el PATH contiene el nombre del usuario después de /home. Mi nombre de usuario es atopos; el del lector será el que sea. El segundo también es obvio: todas las rutas están separadas por ':' ---no lo olvide el lector. El tercero es el más interesante: se han de incluir las rutas ya existentes en la variable PATH antes de cambiarla, de lo contrario, todos las rutas predefinidas de las órdenes habituales serían invisibles para HAL y HAL dejaría de reconocer como órdenes a nuestros viejos amigos echo, sed, grep, awk y compañía.

Esta última consideración puede llevar a algún lector a una deducción inteligente. Si la variable PATH ya tiene un valor antes de nuestro cambio ---un valor que, como hemos visto, se designa con $PATH--- y este valor debe ser preservado, ¿por qué no añadir directamente la ruta de nuestro directorio guiones a ese valor previo? Dicho más claramente, ¿por qué no poner en lugar de la larga expresión anterior esta otra?

PATH=$PATH:/home/atopos/guiones

Si el lector ha llegado a tal conclusión por sí mismo, ¡enhorabuena! Demuestra una inteligencia despierta y creativa y una comprensión plena de lo dicho en este artículo. Efectivamente, ésa es la forma habitual de modificar variables cuyo valor anterior debe mantenerse. Por tanto, nuestra forma de pedir a HAL que ejecute prueba debe ser la siguiente:

PATH=$PATH:/home/atopos/guiones prueba

Y, ahora sí ---por fin--- la respuesta de HAL cambia, pero de una forma totalmente inesperada:

bash: /home/atopos/guiones/prueba: Permiso denegado

Increíble, pero cierto. Incluso estando en nuestro home particular, HAL nos dice que no tenemos permisos. ¡Esto sí que es un galimatías!

Pero no, lector, HAL no ha dejado de funcionar cuerdamente. La razón de su comportamiento es muy significativa y será tratada en el próximo artículo.

Antes de ello, conviene comentar tres cosas más en relación con el asunto de este artículo.

La primera es que se podría editar nuestro fichero .bashrc para que el cambio de la variable PATH fuera permanente, de modo semejante a como se hizo en HAL y mis ficheros con las variables del entorno local.

La segunda es que podríamos incluir nuestros propios guiones en los directorios que el sistema tiene reservados para los binarios ejecutables. Algo muy poco recomendable, porque, aunque ello evita tocar la variable PATH, acaba mezclando órdenes del sistema con órdenes de usuario y las consecuencias de esta mezcolanza pueden ser bastante penosas, aparte de poco estéticas.

La tercera y última es que existe otra forma sencilla para conseguir que HAL entienda mis guiones como ficheros ejecutables sin necesidad de redefinir la variable PATH ni de añadir nada ajeno a los directorios del sistema. A saber, dar como orden la ruta completa del fichero que contiene la orden. En el caso de nuestro fichero prueba sería:

/home/atopos/guiones/prueba

Como esta opción es un tanto molesta, por larga, existe un atajo o, más exactamente, una abreviatura: hacer preceder la orden del caso con los signos punto y barra: './'. Es decir, que sin hacer ninguna modificación en la variable PATH y dando por sobrentendido que estamos dentro del directorio guiones podríamos escribir:

./prueba

Tanto la versión que incluye la ruta completa como esta abreviada (donde el punto es la abreviatura del directorio actual de trabajo) obtendríamos el mismo resultado inesperado de la orden anterior en que la variable PATH fue modificada.

De hecho, y en próximos artículos, utilizaremos este último recurso (./mi-orden) con el fin de diferenciar nuestras órdenes de las que proporciona el sistema.


Resumen

  • Cuando escribimos una orden en la línea de órdenes, HAL comprueba si el fichero que corresponde a esa orden existe en el sistema. Lo hace consultando los directorios definidos en la variable de entorno PATH.

  • La orden printenv (print environment) devuelve el valor actual de las variables de entorno que se le especifican como argumentos. Por defecto, esto es, sin argumentos, devuelve todas las variables del entorno y sus valores actuales.

  • Una variable es un nombre para un determinado fragmento de información.

  • El valor de una variable se designa mediante el nombre de esa variable precedido por el prefijo '$'. Por ejemplo, $PATH designa el valor de la variable PATH.

  • Cuando el argumento de echo es la designación de un valor de variable, echo devuelve el valor de esa variable. Por ejemplo, echo $PATH devuelve el valor de la variable PATH.

  • La combinación de signos './' como prefijo de un nombre de fichero, hace que ese nombre sea interpretado como el nombre de una orden existente en el directorio de trabajo actual. (El punto está ahí como abreviatura de la ruta completa de dicho directorio).