sábado, 6 de junio de 2009

HAL y la burocracia (XII - limpieza)

Los buenos profesionales de la construcción, la fontanería o de cualquier otra actividad que interviene en la infraestructura de nuestra casa no dan por finalizado su trabajo hasta haber borrado completamente la huellas de su actividad reformadora. Hoy en día, urgidos por el afán de amasar dinero fácil o, simplemente, carentes de toda ética profesional, son habituales los chapuceros que nos acaban dejando la casa hecha un zaguán.

No vayamos a emular esta desagradable actitud y procuremos que tras la ejecución de nuestro guión todo quede limpio y bien dispuesto.

Por lo general, un guión genera dos tipos de basura: basura permanente, compuesta por ficheros temporales, ya sea creados por nosotros mismos o por las órdenes que lo componen, y basura en pantalla, esto es, mensajes de información que se producen al ejecutar dichas órdenes. Los primeros se eliminan fácilmente con la orden rm; los segundos, redirigiéndolos al agujero negro de HAL.

Estas operaciones de limpieza deben efectuarse solamente una vez que se ha comprobado fehacientemente la corrección de nuestro guión. De hecho, los ficheros temporales y los mensajes producidos en su ejecución son información valiosa para descubrir la fuente de errores potenciales. Y ---conviene recordarlo--- no es probable que un guión esté libre de fallos en su primeras redacciones.

El paso de eliminar los ficheros temporales es, a estas alturas, pan comido. Los ficheros temporales creados por nuestro guión son:

  • Los ficheros objetivos_tmp.tex, contenidos_tmp.tex y trabajo_tmp.tex

  • Los ficheros producidos por pdflatex, cuyo nombre es el mismo que el del fichero pdf resultante: $ALUMNO.pdf, pero con extensiones diferentes: $ALUMNO.tex, $ALUMNO.aux, etc.


La orden para eliminar los ficheros citados en primer lugar puede ser ésta, que borra todos los ficheros cuyo nombre termina en _tmp.tex:

rm *_tmp.tex

Para eliminar los ficheros citados en segundo lugar, podemos utilizar una orden como la siguiente:

rm $(ls ${ALUMNO}* | grep -v '\.pdf')

Esta orden envía la lista de todos los ficheros cuyo nombre comienza por $ALUMNO a grep, que devuelve todos menos el que termina con la extensión pdf ---para eso sirve la opción -v de grep---, y lo filtrado por grep se lo da como argumento a rm.

Para que los mensajes de información emitidos por las órdenes que se ejecutan dentro del guión no aparezcan en pantalla, el recurso habitual es redirigirlos a un fichero especial, llamado /dev/null, un agujero negro dentro de HAL que succiona todo lo que llega a él y lo hace desaparecer en la nada. De las órdenes incorporadas en nuestro guión, pdflatex va a producir siempre mensajes informativos, como habrá comprobado el lector que haya puesto a prueba las versiones del guión dadas hasta ahora. Si no queremos molestar al usuario con estos mensajes, podemos añadir la redirección a /dev/null a dicha orden:

pdflatex ${ALUMNO}.tex > /dev/null

La nueva versión del guión, generar_informes-5, con estas tres medidas higiénicas incluidas, queda así:

# Directorio que contiene la programación del curso
PROGRAMACION="$HOME/guiones/informe_suspensos"

# Fichero que contiene la plantilla LaTeX del informe
PLANTILLA=informe_plantilla.tex

# Fichero que contiene las notas de los alumnos
NOTAS=notas

# Sufijo de los ficheros de objetivos por cursos
OBJETIVOS=objetivos.tex

# Sufijo de los ficheros de contenidos por cursos
CONTENIDOS=minimos.tex

# La nota mínima para aprobar el curso
NOTA_MINIMA=5

# Fichero sed para limpiar etiquetas LaTeX
NOLATEX=limpiar_tex.sed

for linea in $(cat $NOTAS)
do
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
# Nombre y curso del alumno tal como constan en el fichero $NOTAS
ALUMNO=$(echo $linea | cut -d':' -f1)
CURSO=$(echo $linea | cut -d':' -f2)

# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(echo $ALUMNO | sed -e 's/-/ /g')
CURSO_OUTPUT=$(echo $CURSO | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Ficheros que contienen los objetivos y contenidos del curso
# en que el alumno está matriculado
F_OBJETIVOS=${PROGRAMACION}/${CURSO}-$OBJETIVOS
F_CONTENIDOS=${PROGRAMACION}/${CURSO}-$CONTENIDOS

# Los objetivos y contenidos en un formato apto para la plantilla
# a partir de la que se genera el informe
sed -f $NOLATEX $F_OBJETIVOS > objetivos_tmp.tex
sed -f $NOLATEX $F_CONTENIDOS > contenidos_tmp.tex

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{objetivos_tmp}/' \
-e 's/CONTENIDOS/\\input{contenidos_tmp}/' \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

pdflatex ${ALUMNO}.tex > /dev/null

# Eliminación del los ficheros temporales creados
rm *_tmp.tex
rm $(ls ${ALUMNO}* | grep -v '\.pdf')

fi
done

Las tres únicas diferencias dignas de mención respecto de la versión propuesta al principio de esta serie son:

  • El valor de la variable PROGRAMACION, un valor que lógicamente dependerá del lugar donde el usuario tenga almacenados los ficheros de objetivos y mínimos.

  • La instrucción lp ${ALUMNO}.pdf > /dev/null, que envía el informe del alumno directamente a la impresora, en caso de que el usuario disponga de un sistema de impresión basado en CUPS, lo normal estos días en las distribuciones GNU/Linux.

  • El enigmático #!/bin/sh, que nos acompaña desde el pasado de estas conversaciones, que no es estrictamente necesario en la mayoría de los casos, y cuyo sentido tocará explicar en su momento.


Si el lector ha sido capaz de alcanzar esta cota sin desfallecer, puede considerarse un usuario avanzado y, si le apetece verlo de esta otra forma, un programador en ciernes. Enhorabuena, Sancho, te subiremos la nota.


Resumen:

  • Cuando la corrección de un guión se ha comprobado suficientemente, es recomendable limpiar el directorio desde el que se ejecuta de todos los ficheros temporales generados y suprimir de la interfaz del usuario los mensajes informativos que las órdenes aplicadas pueden generar.

  • La opción -v de grep invierte el sentido de la búsqueda, esto es, devuelve las líneas de la entrada que no encajan con el patrón suministrado como primer argumento.

  • El fichero especial /dev/null existe para redirigir a él cualquier salida de nuestras órdenes que queremos hacer desaparecer del universo visible.

viernes, 5 de junio de 2009

HAL y la burocracia (XI)

Entre el guión original y la última versión obtenida en el proceso de reconstrucción que estamos siguiendo, hay ya muy pocas diferencias, todas ellas relativamente triviales.

En primer lugar, y con objeto de aliviar la exposición, hemos partido de un presupuesto falso, a saber, que los ficheros reales de objetivos y mínimos ---los del autor--- contenían sólo la etiqueta de LaTeX \item. La realidad es un poco más compleja. En tales ficheros aparecían, además, las etiquetas: \section, \subsection, \subsubsection, \paragraph, \begin{itemize} y \end{itemize}. Ahora bien, la plantilla a partir de la cual se generan los informes particulares de cada alumno cuenta con que los ficheros incluidos mediante \input en los entornos Objetivos y Contenidos constan de líneas precedidas por la etiqueta \item y que ninguna otra etiqueta LaTeX está presente. Esto significa que los ficheros originales curso-objetivos.tex y curso-minimos.tex deben sufrir un proceso de depuración antes de ser incluidos mediante \input. Una forma sencilla de lograrlo es utilizar una instrucción sed que elimine todas las etiquetas mencionadas, excepto \item:

sed -e '/\\.*section/d' \
-e '/\\.itemize/d' \
-e '/\\paragraph/d' ${CURSO}-objetivos.tex

sed -e '/\\.*section/d' \
-e '/\\.itemize/d' \
-e '/\\paragraph/d' ${CURSO}-minimos.tex

Puesto que el proceso de depuración debe aplicarse, como acabamos de ver, tanto al fichero de objetivos como al de mínimos, y puesto que, como aprendimos ayer, hay que rehuir de las redundancias, es oportuno utilizar aquí un fichero que contenga las expresiones sed anteriormente definidas y que se pase como argumento a sed (mediante su opción -f) en sendas órdenes de depuración.

Creamos, pues, el fichero que llamaremos limpiar_tex.sed, cuyo contenido será:

/\\.*section/d
/\\.*itemize/d
/\\paragraph/d

Y, dado que el fichero en cuestión es un dato de entrada al mismo nivel que el fichero $NOTAS o el directorio $PROGRAMACION, una variable inicial que se refiera al nuevo fichero parece lo más indicado:

# Fichero sed para limpiar etiquetas LaTeX
NOLATEX=limpiar_tex.sed


Ahora podemos pasar ese fichero como argumento de la opción -f de sed para obtener los ficheros de objetivos y mínimos que se incluirán en los ficheros .tex resultantes de las conversiones realizadas sobre la plantilla:

sed -f $NOLATEX ${CURSO}-$OBJETIVOS > objetivos_tmp.tex
sed -f $NOLATEX ${CURSO}-$CONTENIDOS > contenidos_tmp.tex

Se habrá notado que el resultado se redirige a un par de ficheros temporales, que serán, en último término, los que se incluirán en el fichero .tex de cada informe. Es evidente que es esto lo que queremos y no, por ejemplo, sobreescribir los ficheros originales de objetivos y mínimos.

Para mayor claridad y versatilidad, se pueden definir dos variables puestas al valor del nombre del fichero de objetivos y mínimos de cada caso, variables que pueden a su vez pasarse como argumentos de las órdenes sed anteriormente citadas en el lugar de los propuestos antes:

# Ficheros que contienen los objetivos y contenidos del curso
# en que el alumno está matriculado
F_OBJETIVOS=${PROGRAMACION}/${CURSO}-$OBJETIVOS
F_CONTENIDOS=${PROGRAMACION}/${CURSO}-$CONTENIDOS


# Los objetivos y contenidos en un formato apto para la plantilla
# a partir de la que se genera el informe
sed -f $NOLATEX $F_OBJETIVOS > objetivos_tmp.tex
sed -f $NOLATEX $F_CONTENIDOS > contenidos_tmp.tex


Lo que resta es, simplemente, modificar adecuadamente, las expresiones correspondientes de la última orden sed, que, claramente, acaba adquiriendo un aspecto mucho más amigable (sin las comillas dobles, sin variables y sin la triple barra invertida):

# Versión previa
sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e "s/OBJETIVOS/\\\input{${CURSO}-${OBJETIVOS}}/" \
-e "s/CONTENIDOS/\\\input{${CURSO}-${CONTENIDOS}}/" \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

# Nueva versión
sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{objetivos_tmp}/' \
-e 's/CONTENIDOS/\\input{contenidos_tmp}/' \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

La nueva versión del guión, generar_informes-4, con estas incorporaciones de última hora, queda como sigue:

# Directorio que contiene la programación del curso
PROGRAMACION="$HOME/guiones/informe_suspensos"

# Fichero que contiene la plantilla LaTeX del informe
PLANTILLA=informe_plantilla.tex

# Fichero que contiene las notas de los alumnos
NOTAS=notas

# Sufijo de los ficheros de objetivos por cursos
OBJETIVOS=objetivos.tex

# Sufijo de los ficheros de contenidos por cursos
CONTENIDOS=minimos.tex

# La nota mínima para aprobar el curso
NOTA_MINIMA=5

# Fichero sed para limpiar etiquetas LaTeX
NOLATEX=limpiar_tex.sed


for linea in $(cat $NOTAS)
do
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
# Nombre y curso del alumno tal como constan en el fichero $NOTAS
ALUMNO=$(echo $linea | cut -d':' -f1)
CURSO=$(echo $linea | cut -d':' -f2)

# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(echo $ALUMNO | sed -e 's/-/ /g')
CURSO_OUTPUT=$(echo $CURSO | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Ficheros que contienen los objetivos y contenidos del curso
# en que el alumno está matriculado
F_OBJETIVOS=${PROGRAMACION}/${CURSO}-$OBJETIVOS
F_CONTENIDOS=${PROGRAMACION}/${CURSO}-$CONTENIDOS


# Los objetivos y contenidos en un formato apto para la plantilla
# a partir de la que se genera el informe
sed -f $NOLATEX $F_OBJETIVOS > objetivos_tmp.tex
sed -f $NOLATEX $F_CONTENIDOS > contenidos_tmp.tex


# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{objetivos_tmp}/' \
-e 's/CONTENIDOS/\\input{contenidos_tmp}/' \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

pdflatex ${ALUMNO}.tex
fi
done

HAL y la burocracia (X - redundancia)

Si recurríamos a los bucles para evitar la repetición innecesaria del mismo código sobre los elementos de una lista de datos, carece de sentido mantener en el interior del bucle construcciones redundantes. Y nuestra última redacción posee un grado de redundancia intolerable:

...
for linea in $(cat $NOTAS)
do
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(echo $linea | cut -d':' -f1 | sed -e 's/-/ /g')
CURSO_OUTPUT=$(echo $linea | cut -d':' -f2 | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e "s/OBJETIVOS/\\\input{$(echo $linea | cut -d':' -f2)-${OBJETIVOS}}/" \
-e "s/CONTENIDOS/\\\input{$(echo $linea | cut -d':' -f2)-${CONTENIDOS}}/" \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > $(echo $linea | cut -d':' -f1).tex

pdflatex $(echo $linea | cut -d':' -f1).tex
fi
done

Una forma de evitar estas inoperantes y fastidiosas redundancias es convertir el código que se repite en el valor de una variable. Con ello logramos una serie de beneficios claros:

  • El código en cuestión se ejecuta una única vez, en lugar de varias, lo que implica un uso más eficiente de los recursos.

  • La complejidad disminuye, porque donde había una larga tubería habrá ahora un simple nombre de variable.

  • El resultado es más manejable, puesto que si se decide modificar el guión, bastará hacerlo en un solo sitio y no en varios.

  • El resultado es más legible, pues el nombre de la variable, si está bien elegido, será mucho más fácilmente comprensible que la cadena de órdenes por la que está.


Procedamos a empaquetar el código que extrae el primer y segundo campo de cada línea del fichero $NOTAS, nombre de alumno y curso, respectivamente, en una variable adecuada:

# Nombre y curso del alumno tal como constan en el fichero $NOTAS
ALUMNO=$(echo $linea | cut -d':' -f1)
CURSO=$(echo $linea | cut -d':' -f2)

El siguiente paso es incorporar las definiciones de estas variables y modificar las líneas del guión que contenían lo que ahora es el valor de tales variables. Recordemos, por otra parte, que cuando el valor de una variable se ha de manipular dentro de una tubería por un filtro que exige como entrada una cadena de caracteres se lo pasamos como argumento a echo:

for linea in $(cat $NOTAS)
do
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
# Nombre y curso del alumno tal como constan en el fichero $NOTAS
ALUMNO=$(echo $linea | cut -d':' -f1)
CURSO=$(echo $linea | cut -d':' -f2)

# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(echo $ALUMNO | sed -e 's/-/ /g')
CURSO_OUTPUT=$(echo $CURSO | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e "s/OBJETIVOS/\\\input{${CURSO}-${OBJETIVOS}}/" \
-e "s/CONTENIDOS/\\\input{${CURSO}-${CONTENIDOS}}/" \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

pdflatex ${ALUMNO}.tex
fi
done

Veamos qué dice diff si guardamos esta nueva versión con el nombre generar_informes-3:

diff -Bbi -u generar_informes-2 generar_informes-3

Su respuesta es la siguiente:

--- generar_informes-2 2009-06-05 15:20:33.000000000 +0200
+++ generar_informes-3 2009-06-05 21:38:40.000000000 +0200
@@ -20,9 +20,13 @@
do
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
+ # Nombre y curso del alumno tal como constan en el fichero $NOTAS
+ ALUMNO=$(echo $linea | cut -d':' -f1)
+ CURSO=$(echo $linea | cut -d':' -f2)
+
# El nombre del alumno y curso con el formato que tendrán en la salida impresa
- ALUMNO_OUTPUT=$(echo $linea | cut -d':' -f1 | sed -e 's/-/ /g')
- CURSO_OUTPUT=$(echo $linea | cut -d':' -f2 | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
+ ALUMNO_OUTPUT=$(echo $ALUMNO | sed -e 's/-/ /g')
+ CURSO_OUTPUT=$(echo $CURSO | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

@@ -32,11 +36,10 @@

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
- -e "s/OBJETIVOS/\\\input{$(echo $linea | cut -d':' -f2)-${OBJETIVOS}}/" \
- -e "s/CONTENIDOS/\\\input{$(echo $linea | cut -d':' -f2)-${CONTENIDOS}}/" \
- -e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > $(echo $linea | cut -d':' -f1).tex
+ -e "s/OBJETIVOS/\\\input{${CURSO}-${OBJETIVOS}}/" \
+ -e "s/CONTENIDOS/\\\input{${CURSO}-${CONTENIDOS}}/" \
+ -e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > ${ALUMNO}.tex

- pdflatex $(echo $linea | cut -d':' -f1).tex
+ pdflatex ${ALUMNO}.tex
fi
done


Resumen

  • Por regla general, las redundancias deben ser evitadas en la escritura de guiones o programas.

jueves, 4 de junio de 2009

HAL y la burocracia (IX - repeticiones)

Quizá la ventaja más señalada de un computador respecto del humano es su capacidad de repetir una y otra vez las mismas operaciones sin acusar aburrimiento o cansancio alguno. No hace falta escarbar en las zonas profundas e invisibles de su trastienda para asistir a un aquelarre de incontables repeticiones, incluso la mayoría de las órdenes amigables que hemos aprendido no son sino procesos en los que una misma operación se repite hasta agotar todos los datos de la entrada. Un grep, por ejemplo, no es otra cosa que la repetición de la misma operación, la de buscar en una línea la presencia de un patrón textual, aplicada a todas las líneas de los ficheros que se le dan como argumentos.

Aunque nuestros guiones no han aplicado conscientemente hasta ahora esta facultad repetitiva, el guión del que nos ocupamos estos días es un buen pretexto para conocer algunas herramientas lingüísticas nuevas que permitan programar repeticiones o bucles, como se denominan técnicamente.

Veamos, para empezar, un ejemplo fácil de bucle.

Supongamos que queremos que HAL realice una copia de los ficheros que existen en nuestro subdirectorio actual (~/guiones/informe_suspensos). Nuestro objetivo, en concreto, es que para cada fichero del subdirectorio se obtenga una copia con el nombre fichero.copia. Carece de sentido hacerlo a mano:

cp 1-ge-minimos.tex 1-ge-minimos.tex.copia
cp 1-ge-objetivos.tex 1-ge-objetivos.tex.copia
...

En lugar de ello, podemos utilizar una orden de este tipo:

Para cada 'fichero' en la lista de todos los ficheros del directorio actual, haz una copia con el nombre 'fichero.copia'

O, dispuesta cada expresión en una línea independiente:

Para cada 'fichero' en la lista de todos los ficheros del directorio actual
haz
una copia con el nombre 'fichero.copia'

Podemos traducir, sin más, parte de las expresiones actuales:

Para cada 'fichero' en $(ls *)
haz
cp 'fichero' fichero.copia

La palabra entrecomillada ('fichero') tiene todo el aspecto de ser una variable temporal, pues su valor cambiará según el fichero del caso. Por tanto, podemos confiar en que la siguiente versión es adecuada:

Para fichero en $(ls *)
haz
cp $fichero $fichero.copia

Aunque adecuada, la última línea de esta traducción contiene una ambigüedad. ¿Cuál es el nombre de la variable que aparece en último lugar? Para HAL será todo lo que hay después del signo '$', o sea, fichero.copia. Sin embargo, lo que pretendemos es que la variable sea fichero y .copia un sufijo que se añadirá a cada nombre de fichero concreto. En estos casos, se utilizan llaves para delimitar con precisión absoluta el nombre de la variable: ${fichero}.copia. Mantener la ambigüedad aquí no va a tener efecto negativo, pero conviene irse acostumbrando a estas sutilezas, cuya omisión puede ser letal en otras ocasiones. El fragmento anterior queda, pues, suprimida la ambigüedad, de esta forma:

Para fichero en $(ls *)
haz
cp $fichero ${fichero}.copia

Lo nuevo es poco más que inglés de andar por casa:

for fichero in $(ls *)
do
cp $fichero ${fichero}.copia

un pequeño elemento ortográfico más, el done con que se cierra el bucle:

for fichero in $(ls *)
do
cp $fichero ${fichero}.copia
done

y el, aunque opcional, siempre recomendable, toque de estilo:

for fichero in $(ls *)
do
cp $fichero ${fichero}.copia
done

Para el caso concreto del ejemplo, el código se puede abreviar, eliminando la sustitución de comandos y utilizando únicamente el comodín en su lugar, que en un bucle for tienen el mismo significado [Ver, además, la matización de Vicho en el primer comentario al artículo, si se quiere redondear la sintaxis definitiva]:

for fichero in *
do
cp $fichero ${fichero}.copia
done

Volvamos a nuestro guión provistos de estos nuevos conocimientos.

Se recordará que el modelo para el caso simple contaba con que el fichero notas sólo contuviera una única línea. Vamos a eliminar esta última restricción y aceptar cualquier número posible de alumnos registrados. Digamos que nuestro fichero notas contiene las siguientes líneas:

Don-Quijote-de-la-Mancha:6-gm:10
Sancho-Panza:1-ge:3
Sansón-Carrasco:3-gm:4
Dulcinea-del-Toboso:1-gm:5

El objetivo es que para cada línea del fichero, se ejecuten las instrucciones contenidas en el modelo simple. Recordemos que la versión final de este modelo simple realizaba, por este orden, las siguientes operaciones:

# Verificar que el campo tercero ---la nota--- de la línea del
# fichero sea menor que la nota mínima.
if [ $(cut -d':' -f3 $NOTAS) -lt $NOTA_MINIMA ]
...

### ( En el caso de que se cumpla la condición )
# Convertir el campo primero ---el nombre del alumno--- de la línea
# del fichero al formato que tendrá en la salida impresa y
# guardarlo en la variable 'ALUMNO_OUTPUT'.
ALUMNO_OUTPUT=$(cut -d':' -f1 $NOTAS | sed -e 's/-/ /g')

# Convertir el campo segundo ---el código del curso--- de la línea
# del fichero al formato que tendrá en la salida impresa y
# guardarlo en la variable 'ALUMNO_OUTPUT'.
CURSO_OUTPUT=$(cut -d':' -f2 $NOTAS | sed ...)

# Abrir una sesión interactiva para crear el fichero temporal
# 'trabajo_tmp.tex' que contenga las propuestas de actividades.
...
cat > trabajo_tmp.tex

# Realizar las sustituciones necesarias en el fichero '$PLANTILLA'
# y generar como resultado el fichero 'Sancho-Panza.tex'.
sed ... $PLANTILLA > Sancho-Panza.tex

# Procesar el fichero que resulta del paso anterior con pdflatex.
pdflatex Sancho-Panza.tex

Comencemos, pues, las modificaciones.

El primer paso es crear el bucle que permita realizar esta serie de procesos para cada línea del fichero $NOTAS:

for linea in $(cat $NOTAS)
do

acciones
done

Se observará que para que el guión pueda leer cada línea del fichero y actuar sobre cada una de ellas, tenemos primero que darle acceso a su contenido. cat es una orden que se puede utilizar para este propósito.

El segundo paso es cambiar adecuadamente las entradas de las órdenes que, en nuestra versión previa, accedían al fichero $NOTAS. Puesto que ahora deben ejecutarse no para el fichero en cuanto unidad cerrada, sino para cada una de sus líneas, tendremos que cambiar el método de entrada del dato, ya no vale utilizar el nombre del fichero como argumento, sino que se requiere pasar cada línea a la orden correspondiente mediante una tubería. Veamos las órdenes afectadas en su versión original (en verde) y en su versión modificada (en rojo).

### 1.
# Versión previa
if [ $(cut -d':' -f3 $NOTAS) -lt $NOTA_MINIMA ]

# Nueva versión
if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]

### 2.
# Versión previa
ALUMNO_OUTPUT=$(cut -d':' -f1 $NOTAS | sed -e 's/-/ /g')

# Nueva versión
ALUMNO_OUTPUT=$(echo $linea | cut -d':' -f1 | sed -e 's/-/ /g')

### 3.
# Versión previa
CURSO_OUTPUT=$(cut -d':' -f2 $NOTAS | sed ...)

# Nueva versión
CURSO_OUTPUT=$(echo $linea | cut -d':' -f2 | sed ...)

Una tercera modificación obligatoria en sustituir en las órdenes correspondientes los nombres de ficheros que aludían a Sancho Panza por una expresión que devuelva el nombre del alumno según la línea del fichero que en ese momento de ejecución del bucle se esté procesando:

### 1.
# Versión previa
sed ... -e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > Sancho-Panza.tex

# Nueva versión
sed ... -e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > $(echo $linea | cut -d':' -f1).tex

### 2.
# Versión previa
pdflatex Sancho-Panza.tex

# Nueva versión
pdflatex $(echo $linea | cut -d':' -f1).tex

El último paso para la generalización del guión es procurar un nombre genérico para los ficheros de objetivos y contenidos mínimos, de forma que se acceda al fichero que corresponda al curso del alumno cuya línea se esté procesando en cada momento. Ya, de paso, recurriremos, para construir el nombre genérico de dichos ficheros, a las variables iniciales OBJETIVOS y CONTENIDOS, no referidas en el guión hasta ahora:

# Versión previa
sed ... -e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
-e 's/CONTENIDOS/\\input{1-ge-minimos}/' ...

# Nueva versión
sed ... -e "s/OBJETIVOS/\\\input{$(echo $linea | cut -d':' -f2)-${OBJETIVOS}}/" \
-e "s/CONTENIDOS/\\\input{$(echo $linea | cut -d':' -f2)-${CONTENIDOS}}/" ...

Se advertirá que esta modificación en la secuencia de reemplazo de las ordenes sed nos obliga a utilizar comillas dobles. Además, puesto que el carácter '\', al igual que '$', mantiene su significado especial cuando va seguido de '\' y dentro de comillas dobles, es necesario volver a escaparlo, lo cual produce el triple '\\\' (!)

Si unimos todas las modificaciones obtenemos esta versión inicial, realmente abigarrada, de nuestro guión generalizado para un fichero $NOTAS con un número arbitrario de líneas:

# Directorio que contiene la programación del curso
PROGRAMACION="$HOME/guiones/informe_suspensos"

# Fichero que contiene la plantilla LaTeX del informe
PLANTILLA=informe_plantilla.tex

# Fichero que contiene las notas de los alumnos
NOTAS=notas

# Sufijo de los ficheros de objetivos por cursos
OBJETIVOS=objetivos.tex

# Sufijo de los ficheros de contenidos por cursos
CONTENIDOS=minimos.tex

# La nota mínima para aprobar el curso
NOTA_MINIMA=5

for linea in $(cat $NOTAS)
do

if [ $(echo $linea | cut -d':' -f3) -lt $NOTA_MINIMA ]
then
# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(echo $linea | cut -d':' -f1 | sed -e 's/-/ /g')
CURSO_OUTPUT=$(echo $linea | cut -d':' -f2 | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e "s/OBJETIVOS/\\\input{$(echo $linea | cut -d':' -f2)-${OBJETIVOS}}/" \
-e "s/CONTENIDOS/\\\input{$(echo $linea | cut -d':' -f2)-${CONTENIDOS}}/" \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > $(echo $linea | cut -d':' -f1).tex

pdflatex $(echo $linea | cut -d':' -f1).tex
fi
done

El autor advierte que la versión no es definitiva, hay demasiadas repeticiones y demasiadas complejidades como para mantenerla así, por muy sucios y rápidos que pretendamos ser. En realidad, esta versión no es ni siquiera la primera que puede surgir en la mente de quien la crea. Como se verá, ante complejidades evidentes, la mente tiende a simplificar desde las fases iniciales.

Poner a prueba esta versión pasa, naturalmente, por disponer, en el directorio desde el se ejecuta el guión, de los ficheros de objetivos y mínimos correspondientes a los cursos en que están matriculados los alumnos suspensos. Así, para el quijotesco ejemplo de cuatro personajes que dimos hace un momento serían necesarios los citados ficheros para 1-ge, que ya habíamos redactado en anteriores sesiones, y para 3-gm, cuya redacción es tarea del lector.

Para finalizar la densa sesión de hoy puede ser interesante introducir una nueva orden de HAL que nos permita observar de un vistazo las modificaciones que vamos realizando en las distintas versiones de nuestros guiones. Una herramienta clásica de HAL para este propósito es diff. Otra, es colordiff, que, en sistemas Debian, es seguramente necesario instalar previamente. Si la primera versión completa del guión, la versión para el caso simple, la guardamos como generar_informes-1 y ésta nueva como generar_informes-2, las diferencias entre ambas versiones se puede mostrar mediante la instrucción [las opciones añadidas son las mismas que las de diff y se pueden consultar en la página de manual correspondiente]:

colordiff -u -Bbi generar_informes-1 generar_informes-2

Que produce el resultado que aparece en esta imagen:



Resumen

  • Los procesos repetitivos pueden requerir y muchas veces exigen el uso de construcciones especiales, denominadas bucles. Un bucle típico es el bucle for, que se atiene a la siguiente sintáxis:

    for variable in lista
    do
    acciones
    done

  • La orden diff ---o su versión coloreada, colordiff--- permite contemplar las diferencias existentes entre dos ficheros.

HAL y la burocracia (VIII - decisiones)

La vida es inconcebible como camino de dirección única. A cada paso se nos abre un sinfín de posibilidades, muchas veces incompatibles, ante las cuales no nos queda más remedio que elegir. Los senderos se bifurcan en el ambiguo jardín de la existencia. Pero nuestras elecciones no son, o no deberían ser, completamente caprichosas. La consideración reflexiva de las opciones presentes en las encrucijadas guían nuestra conducta. Por eso, una lengua que careciese de oraciones condicionales no sería humana o, cuando menos, no sería aplicable más que a un sector insignificante de la realidad vital.

Tampoco a la lengua de HAL le son ajenas las peripecias de la decisión, pues siempre llega el caso en que, dadas ciertas circunstancias, toca desviarse de la senda principal e iniciar una nueva. Por suerte, los "si es" y "si no es" de HAL son mucho más fáciles de aprender que las normas de la consecutio temporum, y las ambigüedades en las alternativas que presentan están terminantemente prohibidas, so pena de naufragar en resultados ininteligibles o simplemente abortados.

Consideremos un ejemplo trivial, para irnos acostumbrando a esta nueva rama de nuestro acervo lingüístico:

Si estoy en casa, di "hola"; si no, di "adiós".

Pongamos cada frase en una línea aparte para más claro entendimiento:

Si estoy en casa
di hola
si no
di adios

Las oraciones imperativas sabemos cómo expresarlas, son el meollo de todo lo que hemos pronunciado hasta ahora:

Si estoy en casa
echo "hola"
si no
echo "adiós"

También podemos construir un equivalente en lengua de HAL para la condición estipulada, la de estar en casa. Decir que estamos en casa es, visto desde la perspectiva de HAL, lo mismo que decir que nuestro directorio actual de trabajo es igual que nuestro directorio home:

Si $(pwd) = $HOME
echo "hola"
si no
echo "adiós"

Es fundamental advertir que el '=' en la frase $(pwd) = $HOME debe estar separado de los términos que compara por un espacio y que es, a pesar de su apariencia, completamente distinto del '=' de una expresión de asignación de valor a una variable (por ejemplo, ESCUDERO='Sancho Panza') donde los espacios de separación están prohibidos. La primera expresión compara dos cadenas de caracteres por su igualdad, mientras que la segunda asigna un valor a una variable.

El resto es el inglés corriente y moliente:

if $(pwd) = $HOME
echo "hola"
else
echo "adiós"

Finalmente, unas pocas normas ortográficas más: la condición va entre corchetes y a un espacio de distancia por delante y por detrás de ellos; tras el "si" (el if) ha de haber un "entonces" (un then); para terminar, la frase entera se cierra graciosamente con un if invertido (fi):

if [ $(pwd) = $HOME ]
then
echo "hola"
else
echo "adiós"
fi

Y, como convención de estilo, un bello espaciado que destaque la diferente función de cada ingrediente en la frase:

if [ $(pwd) = $HOME ]
then
echo "hola"
else
echo "adiós"
fi

El lector puede copiar este fragmento de código, pegarlo en su línea de órdenes y ejecutarlo. Si su directorio actual es su HOME, recibirá un caluroso saludo; en caso contrario, un fría despedida.

La facultad de construir oraciones condicionales abre un campo enorme de nuevas posibilidades para nuestros guiones y para nuestras conversaciones con HAL.

La primera aplicación es que en el guión simplificado que hemos producido estos días ya no tenemos por qué seguir suponiendo artificialmente que la nota de nuestro alumno es suspenso. Dicho de otra forma, podemos solicitar a HAL que realice las operaciones establecidas ---que en la redacción actual realiza en todo caso posible--- sólo si la calificación del alumno que consta en el fichero notas es inferior a la nota mínima necesaria para aprobar.

Construyamos, en primer lugar, la condición:

el campo tercero de la línea que consta en el fichero 'notas' ---el que almacena la calificación--- es menor que la nota mínima

En lengua de HAL:

$(cut -d':' -f3 $NOTAS) -lt $NOTA_MINIMA

El operador -lt, abreviatura de less than (menor que), se utiliza precisamente para este tipo de comparaciones aritméticas.

La frase condicional entera sería:

if [ $(cut -d':' -f3 $NOTAS) -lt $NOTA_MINIMA ]
then
ÓRDENES
fi

Como no necesitamos realizar ninguna operación en el caso de que la nota sea superior a la mínima, no es necesario crear una cláusula else para nuestra frase.

El guión completo con la condición añadida quedaría ahora como sigue:

# Directorio que contiene la programación del curso
PROGRAMACION="$HOME/guiones/informe_suspensos"

# Fichero que contiene la plantilla LaTeX del informe
PLANTILLA=informe_plantilla.tex

# Fichero que contiene las notas de los alumnos
NOTAS=notas

# Sufijo de los ficheros de objetivos por cursos
OBJETIVOS=objetivos.tex

# Sufijo de los ficheros de contenidos por cursos
CONTENIDOS=minimos.tex

# La nota mínima para aprobar el curso
NOTA_MINIMA=5

if [ $(cut -d':' -f3 $NOTAS) -lt $NOTA_MINIMA ]
then

# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(cut -d':' -f1 $NOTAS | sed -e 's/-/ /g')
CURSO_OUTPUT=$(cut -d':' -f2 $NOTAS | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
-e 's/CONTENIDOS/\\input{1-ge-minimos}/' \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > Sancho-Panza.tex

pdflatex Sancho-Panza.tex
fi

Si lo ponemos a prueba, no habrá ningún cambio en su comportamiento. El informe Sancho-Panza.pdf se generará idéntico al del día pasado. Pero si cambiamos la nota de Sancho y le damos un aprobado, el guión terminará en silencio sin haber realizado ninguna acción, aparte de la asignación temporal de los valores de las variables, cosa que carece de efectos visibles para nosotros.

Resumen

  • La lengua de HAL dispone de la posibilidad de construir frases condicionales mediante la sintaxis:
    if [ condición ] 
    then
    acciones si la condición se cumple
    else
    acciones si la condición no se cumple
    fi

  • El operador '=' sirve para asignar valores a variables (ejemplo: VAR=val) o, inserto en expresiones condicionales, para comparar por su identidad cadenas de caracteres (ejemplo: cadena1 = cadena2).

  • El operador '-lt' dentro de expresiones condicionales sirve para verificar si un valor aritmético es menor que otro.

miércoles, 3 de junio de 2009

HAL y la burocracia (VII - interactividad)

En un remoto pasado de nuestras conversaciones, enredados en las peculiaridades digestivas de HAL, jugábamos a procurar el alimento de órdenes como grep o cut por la vía directa de lo que llamamos entonces la cañería principal ---el standard input (STDIN), para los redichos--- conectada por defecto con nuestro terminal o consola. En aquel momento se trataba sólo de un curioso experimento preparatorio para comprender el significado y función de las imprescindibles tuberías. El pasatiempo de aquellos primeros contactos con las maravillas de la anatomía de HAL nos vendrá ahora de perlas para añadir interactividad a nuestro guión.

Volvamos a reproducir el experimento, pero ahora con otra orden capaz igualmente de alimentarse a través de la entrada estándar, la orden cat (no confundir con cut). cat es una abreviatura de concatenate y su función inicial es la de concatenar los ficheros que se le den como argumentos e imprimirlos línea a línea en pantalla. Si se le da un único fichero lo imprime tal cual. ¿Pero, qué sucede si no le damos ningún fichero como argumento? Probémoslo:

cat

Tras pulsar 'Enter', nos encontramos en una situación ya familiar, la de poder emitir líneas de texto en la entrada estándar para que cat haga lo que habitualmente hace con las entradas que se le proporcionan, devolverlas tal cual a la salida estándar, que por defecto también está conectada con nuestro terminal. El juego continúa sin fin hasta que solicitamos salir de él con Ctrl+D:

hola
hola
cat
cat
Ctrl+D

Hasta aquí nada nuevo. cat se comporta de una manera bastante estúpida ---pensaría alguno---, se limita a reproducir cada línea introducida, como si se tratase de un echo interactivo.

Parece que algo tan tonto no puede dar mucho de sí. Por el contrario, la utilidad de cat para nuestro propósito se desvela si añadimos a la orden una redirección:

cat >conversacion

Lo único que hace este añadido es redirigir la salida al fichero conversacion. Por tanto, si reproducimos la anterior sesión interactiva, donde la salida de cat ya no la veremos en pantalla ---porque se redirige a ese fichero--- estaremos copiando en él tal cual las líneas que hayamos escrito antes de pulsar Ctrl+D.

Inmediatamente se comprende la utilidad que este comportamiento de cat puede tener para nuestro guión.

Habíamos dejado pendiente la sección correspondiente a las propuestas de actividades que el alumno debe realizar para superar los objetivos y contenidos. Puesto que estas propuestas ---vamos a suponer---, no pueden, en principio, establecerse de antemano, sino que tienen que redactarse de modo expreso para cada alumno suspenso, proporcionar al guión por medio de cat un acceso a la entrada estándar, nos permitirá realizar la redacción de tales propuestas antes de que las sustituciones en la plantilla informe_plantilla.tex tengan efecto.

Esto nos lleva a modificar el guión obtenido el día pasado del modo siguiente [en rojo las nuevas líneas]:

# Directorio que contiene la programación del curso
PROGRAMACION="$HOME/guiones/informe_suspensos"

# Fichero que contiene la plantilla LaTeX del informe
PLANTILLA=informe_plantilla.tex

# Fichero que contiene las notas de los alumnos
NOTAS=notas

# Sufijo de los ficheros de objetivos por cursos
OBJETIVOS=objetivos.tex

# Sufijo de los ficheros de contenidos por cursos
CONTENIDOS=minimos.tex

# La nota mínima para aprobar el curso
NOTA_MINIMA=5

# El nombre del alumno y curso con el formato que tendrán en la salida impresa
ALUMNO_OUTPUT=$(cut -d':' -f1 $NOTAS | sed -e 's/-/ /g')
CURSO_OUTPUT=$(cut -d':' -f2 $NOTAS | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
-e 's/ge/EE/' \
-e 's/gm/EP/')

# Las propuestas de trabajo se deben introducir interactivamente
echo "Introduzca propuestas de trabajo para $ALUMNO_OUTPUT [\item ... (^D para salir)]: "
cat > trabajo_tmp.tex



sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
-e 's/CONTENIDOS/\\input{1-ge-minimos}/' \
-e 's/RECUPERACIÓN/\\input{trabajo_tmp}/' $PLANTILLA > Sancho-Panza.tex

pdflatex Sancho-Panza.tex

La instrucción echo produce un mensaje para el usuario, mientras que la instrucción cat abre la entrada estándar para que el usuario introduzca los datos que se almacenarán en el fichero trabajo_tmp.tex. Este fichero es luego incluido en el fichero Sancho_Panza.tex por obra de la última expresión del sed final.

Se habrá advertido que el usuario debe introducir las propuestas precedidas de '\item', puesto que el entorno LaTeX del que acabarán formando parte es un entorno lista. Esta última complicación se puede evitar fácilmente de forma que al usuario le baste introducir cada propuesta como un línea única (sin el \item). En tal caso, el \item debería añadirlo el propio guión, justo después del cat, con una instrucción como ésta:

sed -i 's/^\(.*\)/\\item \1/' trabajo_tmp.tex

Que la versión presentada en el primer capítulo de esta serie no contenga la simplificación aludida aceleró unos segundos la redacción del guión, aunque a expensas de complicar en parte la interfaz del usuario. Es lo que tiene el modo de obrar "rápido y sucio".

Queda, finalmente, añadir la sección relativa a las propuestas en nuestra plantilla:

...
\begin{document}
\begin{datosAlumno}
Alumno: ALUMNO & CURSO
\end{datosAlumno}
\begin{Objetivos}
OBJETIVOS
\end{Objetivos}
\begin{Contenidos}
CONTENIDOS
\end{Contenidos}
\begin{Propuestas}
RECUPERACIÓN
\end{Propuestas}

\end{document}

A estas alturas, el lector no debería hallar dificultad alguna en realizar por su cuenta las modificaciones que convengan y volver a poner a prueba el guión mejorado.

Resumen

  • La orden cat concatena los ficheros que se le proporcionan como argumentos y los imprime en la salida estándar, por defecto, la pantalla. Si no recibe argumentos toma su entrada de la entrada estándar.

  • Una de las aplicaciones comunes de la orden cat es permitir la creación interactiva de un fichero.

martes, 2 de junio de 2009

HAL y la burocracia (VI)

Antes de dar un paso más en la elaboración de nuestro guión, conviene que el lector lo ponga a prueba, si no lo ha hecho ya por cuenta propia.

Creamos, para empezar un directorio informe_suspensos bajo nuestro subdirectorio de guiones:

mkdir ~/guiones/informe_suspensos

Conviene recordar los ficheros recién creados para nuestra prueba [en caso de que el lector no los haya creado todavía, deberá hacerlo ahora con cualquier editor de texto plano]:

  • informe_plantilla.tex (la plantilla para generar el fichero .tex por alumno [Añado un preámbulo adecuado, modifico ligeramente la sección correspondiente a los datos del alumno y suprimo la sección correspondiente a las propuestas, que ha quedado pendiente, como se recordará]):

    \usepackage[spanish]{babel}
    \usepackage[T1]{fontenc}
    \usepackage[utf8]{inputenc}
    \usepackage{bera}
    \usepackage[usenames,dvipsnames]{color}
    \usepackage{framed}
    \usepackage{enumitem}

    \newenvironment{datosAlumno}%
    {\begin{framed}%
    \begin{center}%
    \begin{tabular*}{\textwidth}%
    {@{\extracolsep{\fill}}lr}}%
    {\end{tabular*}\end{center}\end{framed}}

    \newenvironment{Objetivos}%
    {\begin{framed}%
    \section*{Objetivos}%
    \end{framed}%
    \begin{framed}%
    \begin{itemize}[label=\fbox{\textcolor{white}{X}}]}%
    {\end{itemize}\end{framed}}

    \newenvironment{Contenidos}%
    {\begin{framed}%
    \section*{Contenidos}%
    \end{framed}%
    \begin{framed}%
    \begin{itemize}[label=\fbox{\textcolor{white}{X}}]}%
    {\end{itemize}\end{framed}}

    \newenvironment{Propuestas}%
    {\begin{framed}%
    \section*{Propuestas}%
    \end{framed}%
    \begin{framed}%
    \begin{itemize}}%
    {\end{itemize}\end{framed}}

    \begin{document}
    \begin{datosAlumno}
    Alumno: ALUMNO & CURSO
    \end{datosAlumno}
    \begin{Objetivos}
    OBJETIVOS
    \end{Objetivos}
    \begin{Contenidos}
    CONTENIDOS
    \end{Contenidos}

  • 1-ge-objetivos.tex (el fichero con los objetivos del curso 1-ge):

    \item Reconocer la diferencia entre los refranes y el buen decir.
    \item Adquirir el hábito del buen decir.
    \item Refrenar el hábito malsano del refraneo sin ton ni son.

  • 1-ge-minimos.tex: (el fichero con los contenidos mínimos del curso 1-ge):

    \item El refrán y sus muchos peligros.
    \item El decir del caballero versus el decir del bufón.
    \item Síntomas alarmantes de la verborrea refranil.

  • notas: (el fichero con las notas):

    Sancho-Panza:1-ge:3

  • generar_informes: (el guión propiamente dicho [Unimos lo que obtuvimos en el análisis de datos y en el esbozo del modelo simple, con las comillas corregidas]):

    # Directorio que contiene la programación del curso
    PROGRAMACION="$HOME/guiones/informe_suspensos"

    # Fichero que contiene la plantilla LaTeX del informe
    PLANTILLA=informe_plantilla.tex

    # Fichero que contiene las notas de los alumnos
    NOTAS=notas

    # Sufijo de los ficheros de objetivos por cursos
    OBJETIVOS=objetivos.tex

    # Sufijo de los ficheros de contenidos por cursos
    CONTENIDOS=minimos.tex

    # La nota mínima para aprobar el curso
    NOTA_MINIMA=5

    # El nombre del alumno y curso con el formato que tendrán en la salida impresa
    ALUMNO_OUTPUT=$(cut -d':' -f1 $NOTAS | sed -e 's/-/ /g')
    CURSO_OUTPUT=$(cut -d':' -f2 $NOTAS | sed -e 's/\([[:digit:]]*\)-\(.*\)/\1\.º \2/' \
    -e 's/ge/EE/' \
    -e 's/gm/EP/')


    sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
    -e "s/CURSO/$CURSO_OUTPUT/" \
    -e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
    -e 's/CONTENIDOS/\\input{1-ge-minimos}/' $PLANTILLA > Sancho-Panza.tex

    pdflatex Sancho-Panza.tex

(Nótese que en este último fichero hemos modificado la variable PROGRAMACIÓN para que tenga en cuenta la nueva ubicación de nuestros ficheros de objetivos y mínimos, aunque no la vayamos a usar en las órdenes que hemos producido hasta ahora. También hemos añadido una línea al final, para que, al ejecutarse el guión, pdflatex procese el fichero .tex resultante de la orden sed que le precede.)

Todos estos ficheros deben estar ahora bajo el subdirectorio $HOME/guiones/informe_suspensos

Queda cambiar el modo del guión:

chmod u+x informe_suspensos

y ejecutarlo:

./informe_suspensos

El resultado, aparte de otros ficheros temporales generados por pdflatex, es el fichero Sancho-Panza.pdf, que, en un lector de pdfs como okular, se muestra con el siguiente aspecto:

HAL y la burocracia (V - las comillas)

Como advertíamos ayer, por los pequeños detalles merodea el diabólico encantamiento. Para desenmascararlo, vamos a realizar unos pocos experimentos.

En primer lugar, crearemos un variable y le daremos como valor una cadena:

ESCUDERO='Sancho Panza'

Nótese que las comillas son aquí necesarias. De no haberlas puesto, el espacio, un carácter especial en la lengua de HAL (el delimitador que sirve, por ejemplo, para determinar donde termina una orden y empiezan sus argumentos) hubiese sido interpretado por HAL como un separador. Más exactamente, si hubiésemos escrito:

ESCUDERO=Sancho Panza

HAL hubiese creado la variable ESCUDERO con el valor Sancho y hubiese tratado de ejecutar la inexistente orden Panza, con el consabido resultado de command not found. Ni siquiera la variable ESCUDERO hubiese retenido su valor, Sancho, porque, como sabemos desde hace mucho, la definición de variables en la misma línea y justo delante de una orden sólo afecta a la ejecución de esa orden.

Por tanto, las comillas en la anterior definición tienen una función importantísima, permitir que el espacio se interprete literalmente y no como un signo con un valor sintáctico especial. Lo que, en ese ejemplo concreto, implica que el valor de la variable ESCUDERO será Sancho Panza (espacio incluido), como podemos comprobar con un simple:

echo $ESCUDERO

que devuelve:

Sancho Panza

¿Hubiese sido diferente utilizar dobles comillas en lugar de comillas simples?

Lo probamos:

CABALLERO="Don Quijote"
echo $CABALLERO

Que devuelve, de forma semejante:

Don Quijote

Parece, pues, que no hay ninguna diferencia entre las comillas simples y las dobles comillas, que es cosa más de gusto que de gramática. Pero eso, justamente, es lo que quieren que creamos los pérfidos encantadores. De hecho, nosotros hemos utilizado hasta ahora ambos tipos de comillas sin mayor cuestionamiento. Pero las apariencias engañan. Veámoslo:

echo 'El compañero de $CABALLERO es $ESCUDERO'

produce:

El compañero de $CABALLERO es $ESCUDERO

Es algo que deberíamos haber esperado. Las comillas preservan también aquí el significado literal del carácter '$' y evitan que se interprete como un signo sintáctico especial, a saber, el de proporcionar el valor de la variable que le sigue.

Sin embargo,

echo "El compañero de $CABALLERO es $ESCUDERO"

devuelve, sorprendentemente:

El compañero de Don Quijote es Sancho Panza

La conclusión de nuestro experimento parece obvia, las comillas simples son rejas más seguras que las dobles. En general, de las comillas simples ningún carácter se evade de ser interpretado literalmente, mientras que hay caracteres especiales que no se doblegarán a las comillas dobles y seguirán exigiendo ser interpretados como tales signos sintácticos especiales. Entre estos pocos signos que han conseguido la prevalencia de su característica identidad sintáctica está nuestro querido '$'.

Lo que tan interesante conocimiento implica para el código propuesto al final del día pasado es que pone a las claras el origen de su error. Recordemos cuál era el código:

sed -e 's/ALUMNO/$ALUMNO_OUTPUT/' \
-e 's/CURSO/$CURSO_OUTPUT/' \
-e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
-e 's/CONTENIDOS/\\input{1-ge-minimos}/' $PLANTILLA > Sancho-Panza.tex

Las dos primeras expresiones de la orden sed contienen el signo especial '$'. Ahora bien, la expansión del valor de las variables ALUMNO_OUTPUT y CURSO_OUTPUT no se producirá, puesto que '$' se interpretará literalmente. Acabaremos con un fichero Sancho-Panza.tex con las líneas:

\begin{datosAlumno}
Alumno: $ALUMNO_OUTPUT
Curso: $CURSO_OUTPUT
\end{datosAlumno}

La solución, tras lo aprendido hoy, es bien simple, utilizar dobles comillas en las líneas díscolas:

sed -e "s/ALUMNO/$ALUMNO_OUTPUT/" \
-e "s/CURSO/$CURSO_OUTPUT/" \
-e 's/OBJETIVOS/\\input{1-ge-objetivos}/' \
-e 's/CONTENIDOS/\\input{1-ge-minimos}/' $PLANTILLA > Sancho-Panza.tex

Un lector espabilado podría imaginar una solución incluso más sencilla. Ya que las comillas esconden tantas triquiñuelas, ¿por qué no suprimirlas directamente?:

sed -e s/ALUMNO/$ALUMNO_OUTPUT/ \
-e s/CURSO/$CURSO_OUTPUT/ \
...

Desgraciadamente, no funcionaría, aunque sí funcionaría la siguiente solución:

sed -e s/ALUMNO/"$ALUMNO_OUTPUT"/ \
-e s/CURSO/"$CURSO_OUTPUT"/ \
...

Dejamos como ejercicio para el lector la experimentación y comprensión de las causas del fracaso y el éxito de las dos últimas propuestas.

Resumen

  • Las comillas simples permiten que todos los caracteres especiales de la lengua de HAL encerrados en ellas se interpreten literalmente.

  • Las comillas dobles hacen que casi todos los caracteres especiales de la lengua de HAL se interpreten literalmente, salvo algunos pocos (en especial, el carácter '$'), que seguirán manteniendo su peculiar función sintáctica.