3 de Diciembre, 2004

Las opciones de un script

Este es un aspecto que a veces se descuida un poco y que, en mi opinión, es bastante importante. Nos ayudará a que el script sea más usable y amigable.

Por supuesto no siempre será necesario. Solo tendrá sentido cuando el script tenga distintas opciones que debamos controlar. Si solo necesitamos mostrar una pantalla de ayuda, algo tan sencillo como lo siguiente nos valdría:

if [ $# -eq 1 -a "$1" = "-h" ]; then
	uso
	exit 0
fi

Si el número de parámetros es 1 y además se trata de -h, llamamos a la función uso que muestra la ayuda. Fácil.

Pero cuando hay más parámetros, algunos son modificadores de otros principales, o algunos requieren un argumento a su vez, la cosa se complica bastante.

En realidad no :). Para eso tenemos getopt(1) o getopts como comando interno de nuestro shell compatible con bourne shell (eso incluye a bourne again shell, también conocido como BASH).

Como getopt tiene algunos problemillas, voy a explicar un poco el funcionamiento de la versión incluida en el shell.

La invocación se hace de la siguiente forma:

getopts cadenaops identificador

La cadenaops es una cadena de texto (entre comillas) que contiene las letras de opción que getopts debe reconocer. Si la letra va seguida de dos puntos, se esperará un argumento detrás o pegado, es decir, -ofichero equivale a -o fichero siempre que la o sea la última opción identificada. Así que podremos ver cosas como -rofichero (opciones -r y -o, esta última con argumento fichero).

El identificador es la variable que almacenará la opción encontrada.

Vamos a hacer un ejemplo típico que soporte algunas opciones:

cmd [opciones]
Donde opciones son:
	-h			ayuda
	-r			recursivo
	-o <fichero>		salida a fichero

Cada llamada a getopts procesará un argumento del script, así que tendremos que llamar a esta función hasta que se acaben los argumentos, en cuyo caso getopts devolverá falso. Una estructura while es perfecta para esto.

Para procesar la opción recogida en nuestra variable empleamos una estructura case.

Veamos código:

while getopts "hro:" opcion; do
        case $opcion in

                h)
                        #uso
			echo "Llamaríamos a uso"
                        exit 0
                        ;;

                r)
			flag_r="OK"
			;;

		o)
			flag_o="OK"
			fichero="$OPTARG"
			;;

                *)
                        echo "prueba con -h"
                        exit 1
                        ;;
        esac
done

if [ "X$flag_r" = "XOK" ]; then
	echo "Se introdujo la opcion r"
fi
if [ "X$flag_o" = "XOK" ]; then
	echo "Se introdujo la opcion o (fichero: $fichero)"
fi

En el getopts vemos la cadena de opciones, donde están h, r y o (esta última con argumento, que getopts introduce en la variable OPTARG cuando se detecta) y empleamos la variable opcion para almacenar la opción encontrada.

He empleado variables en el ejemplo para r y o, de forma que se puedan dar las dos a la vez, mientras que para h muestro la ayuda y se acaba el script.

Si hacemos algunas pruebas, obtenemos:

$ ./cmd
$ ./cmd -h
Llamaríamos a uso
$ ./cmd -r
Se introdujo la opcion r
$ ./cmd -o tururu
Se introdujo la opcion o (fichero: tururu)
$ ./cmd -ro uh!
Se introdujo la opcion r
Se introdujo la opcion o (fichero: uh!)
$ ./cmd -o ah! -r
Se introdujo la opcion r
Se introdujo la opcion o (fichero: ah!)
$ ./cmd -h -o mmm -r
Llamaríamos a uso
$ ./cmd -o
./cmd: option requires an argument -- o
prueba con -h
$ ./cmd -k
./cmd: illegal option -- k
prueba con -h

Más o menos creo que los ejemplos hablan por sí solos.

La ejecución sin comandos no muestra nada en el ejemplo, en una aplicación real realizaría su trabajo con valores por defecto para las opciones.

Luego he hecho varias combinaciones, destacando las dos últimas ejecuciones donde vemos qué pasa cuando falta un argumento o cuando se pone una opción no contemplada: tenemos el error de getopts y el nuestro (en el case por defecto).

Y ya solo me queda comentar que solo se identifican opciones cuando hay un guión (-) delante y que getopts no puede manejar opciones largas (para -h sería, por ejemplo, --help).

Ahora ya no hay escusas para no hacer nuestros pequeños programas en shell mucho más usables y amigables :).

Anotación por Juan J. Martínez.

Los comentarios están cerrados: los comentarios se cierran automáticamente una vez pasados 30 días. Si quieres comentar algo acerca de la anotación, puedes hacerlo por e-mail.