miércoles, 21 de julio de 2010

Fijarse en los valores de retorno: #! /bin/bash -eu

Fijarse en los valores de retorno

Mucha gente piensa que bash no es un lenguaje de programación con el que se pueda hacer nada serio. De sintaxis arcaica y llena de trampas, resulta insufrible hasta que lo dominas un poco. Sin embargo, aunque se está virando hacia lenguajes más modernos, es indudable que bash sigue siendo el lenguaje predominante en tareas de administración de sistemas, copias de seguridad etc., por lo que con frecuencia se manejan con este arqueolenguaje procesos críticos.

El hecho de que muchos scripts provengan de sentencias que se han probado previamente en la consola de manera interactiva hace que muchas veces no prestemos atención a cómo iría la ejecución de manera desatendida.

Una de las cuestiones más ignoradas por los nuevos programadores de bash es el código de retorno de los ejecutables invocados. El mecanismo de señalización es muy sencillo: al terminar su ejecución todo binario nos devuelve un entero de 8 bits que nos indica si se ejecutó correctamente. Cero si fue bien, distinto de cero en caso contrario. Más elegante que poner en cada línea un "if" (mecanismo usado por ejemplo en C estándar por la carencia de soporte a manejo de excepciones), es iniciar los scripts con los modificadores "e" y "u" (#! /bin/bash -eu).

-e

indica al intérprete que debe detenerse en cuanto haya un error en cualquier línea.

Esto me parece de crucial importancia, puesto que con carácter general, si en un script falla uno de sus pasos, no existen garantías de que el resto pueda seguir procesándose correctamente. Esto es especialmente importante si estamos cambiando de directorio, o creando ficheros que utilizaremos posteriormente. Las probabilidades de que la ejecución del script sea correcta a pesar de esos fallos es muy baja.

Si tenemos un script largo que hace un proceso complejo de construcción de software, copias, etc, usando esta opción tenemos la ventaja añadida de que si algo falla no tendremos que buscar en un montón de texto de salida: mostrar el error será lo último que haga.

Programar así nos obliga a pensar en el valor de salida de todo lo que ejecutemos, lo cual en la práctica nos hará ser más conscientes al programar.

Un inconveniente es que si queremos ejecutar sentencias que puedan fallar, y no nos importa, tendremos que añadir "|| true".

-u

hace que al intentar expandir una variable no definida, que se produzca un error.

Si además tenemos la opción anterior, el programa parará por completo (si no, sólo se emitirá una advertencia). Esto es importante porque en caso contrario bash expandirá la variable no definida por una cadena vacía. por ejemplo un simple rm -rf $DIR/* se convertiría en rm -rf /*.

Trampa: Pueden surgir problemas al intentar verificar si una variable está definida (if [ "$var" == ""] no funcionará, pues fallará al intentar expandir la variable). La mejor alternativa que he encontrado hasta el momento es [ "${VAR:-}" == ""  ].

No esperéis que funcione todo perfectamente a la primera al usar estas opciones. A veces hay que hacer ajustes, pero valen la pena.

Por supuesto, si algo falla en tu script, notifícalo a quien te invocó: exit N, donde N > 0.