Mostrar ayuda y usar modificadores en Shell Script
En la mayoría de los comandos que usamos, podemos usar modificadores y, casi siempre, escribiendo –help. En este artículo vamos a ver cómo trabajar con los parámetros en Shell Script de manera que podamos mostrar ayuda, recibir valores concretos a través de parámetros independientemente del orden que los escriba el usuario o usar modificadores.
Si trabajamos con parámetros posicionales en los que importa el orden en el que los escribe el usuario, podemos acceder con:
$1
para el primer parámetro
$2
para el segundo parámetro
[…]
${10}
para el décimo parámetro
${11}
para el décimo primer parámetro
Y así sucesivamente o recorrerlos con:
#!/bin/bash for i in "$@" do echo $i done
Pero pecaríamos de idealistas si pensamos que los usuarios van a escribir siempre los parámetros en el orden en el que nosotros hemos decidido. Y cuando programamos, lo hacemos para otras personas humanas, por lo que hemos de hacerlo lo más fácil e intuitivo posible.
Con parámetros posicionales podríamos mostrar la ayuda con algo como esto:
#!/bin/bash function showhelp(){ if [ $(echo $LANG | cut -d "_" -f 1) ] then echo "Muestro ayuda." else echo "Show help." fi } for i in "$@" do if [ "$i" = "--help" ] then showhelp fi done
Pero si tenemos que hacer una gestión más avanzada de los parámetros, como recibir ficheros de entrada y salida, por ejemplo, con -i fichero_entrada y con -o fichero_salida la cosa se complica bastante. Por lo tanto, tenemos que recorrer los parámetros de otra manera. Y aquí es cuando llega el comando shift que permite borrar los parámetros y desplazar un parámetro a la posición anterior.
Es decir, si borramos con shift el primer parámetro ($1), el valor del segundo parámetro ($2) pasa a ocupar $1. Y así con todos los parámetros. Si queremos borrar y desplazar X parámetros, podemos usar shift X.
Veamos cómo usarlo modificando el script anterior:
#!/bin/bash function showhelp(){ if [ $(echo $LANG | cut -d "_" -f 1) ] then echo "Muestro ayuda." else echo "Show help." fi exit 0 } while [ "$#" -ne 0 ] do case $1 in --help) showhelp esac shift done
Los cambios que he hecho respecto al primer script son:
- for i in «$@» -> Recorre la lista de todos los parámetros recibidos.
- while [ «$#» -ne 0 ] -> Ejecuta el bloque de código entre do y done mientras el número de argumentos sea distinto a 0. Si el script recibe parámetros, como los iremos borrando con shift, terminará la ejecución de ese bloque cuando no quede ninguno.
- case $1 in -> Comprueba el valor de $1. De nuevo, como shift va a ir borrando uno a uno los parámetros recibidos y desplazando todos una posición menor cada vez que borre un argumento, el parámetro posicional que nos va a interesar analizar en cada momento siempre será $1.
- –help) -> En el momento que $1 sea igual a –help, ejecutará el código siguiente. En esa ocasión, como sólo he puesto una opción, no he incluido el doble punto y coma (;;), ya que deberá ejecutar todo el código (en este caso, sólo la llamada a la función showhelp) hasta llegar a esac
- shift -> Una vez que ha analizado todos los parámetros, borra el primero para desplazar a una posición anterior todos los demás.
Ahora que ya sabemos cómo mostrar la ayuda, vamos a ver cómo procesar más parámetros, por ejemplo, recibir un fichero de entrada y otro de salida:
#!/bin/bash function showhelp(){ if [ $(echo $LANG | cut -d "_" -f 1) ] then echo "Muestro ayuda." else echo "Show help." fi exit 0 } while [ "$#" -ne 0 ] do case $1 in --help) showhelp ;; -i) input=$2 shift ;; -o) output=$2 shift ;; esac shift done echo "Trabajaremos con $input como fichero de entrada y $output como fichero de salida."
Ahora ya vemos todavía mejor el potencial de shift. Si $1 vale -i o -o significa que el siguiente parámetro es el fichero que queremos usar como fichero de entrada o de salida, respectivamente. Así que asignamos el valor de $2 a la variable correspondiente y borramos una posición con shift y otra vez cuando salimos de case. Así eliminamos $1 y $2 y el siguiente parámetro pasará a ser $1.
Si quisiéramos que un parámetro fuera un modificador del comportamiento del script, por ejemplo, que si indicamos -v el script tuviera un comportamiento verboso, podríamos añadir una variable para gestionarlo pero no escribiríamos un shift dentro de esa opción de case. Y comprobar una vez que hemos terminado de recorrer todos los parámetros, si tenemos todos los valores esperados y, en caso contrario actuar de una u otra manera. Veamos un ejemplo distinto para cada una de las tres opciones que hemos incluido en nuestro script:
#!/bin/bash function showhelp(){ if [ $(echo $LANG | cut -d "_" -f 1) ] then echo "Muestro ayuda." else echo "Show help." fi exit 0 } suffix="_adapted" while [ "$#" -ne 0 ] do case $1 in --help) showhelp ;; -i) input=$2 shift ;; -o) output=$2 shift ;; -v) verbose=1 ;; esac shift done if [ ! "$verbose" ] then verbose=0 fi if [ ! "$input" -o ! -f "$input" ] then if [ $(echo $LANG | cut -d "_" -f 1) ] then echo "Debe incluir un fichero válido de entrada." else echo "You must include a valid input file." fi exit 1 fi if [ ! "$output" -o -f "$output" ] then output=${input%%.*}${suffix}"."${input##*.} fi if [ $verbose -eq 1 ] then echo "Trabajaremos con $input como fichero de entrada y $output como fichero de salida." fi exit 0
De la línea 29 a la 31 he puesto que si el script recibe el modificador -v que la variable verbose valga 1. Y luego he comprobado en las líneas 35-38 si existe esa variable para que, en caso de que no exista, la inicialice a 0 y así poder usarla como en las líneas 56-59.
Otra opción igual de válida, incluso más sencilla, hubiera sido inicializarla a 0 al principio. Pero así vemos cómo comprobar si una variable existe o no. Lo que sí he inicializado al comienzo, en la línea 13 ha sido el sufijo para añadir al nombre del fichero de salida. En caso de que no lo indique el usuario o el fichero ya exista (línea 51) toma el nombre del fichero de entrada (${input%%.*}), le añade el sufijo, el punto y la extensión del fichero de entrada (${input##*.}) en la línea 53.
Sabemos que en ese punto hay un fichero de entrada, porque si no lo hubiera (línea 40), se hubiera acabado el script (línea 48) indicando un mensaje de error en español, si el sistema está configurado en nuestro idioma o en inglés en cualquier otro caso (línea 42).
Con estos pequeños detalles, conseguiremos que nuestros scripts sean mucho más profesionales y, sobre todo, mucho más útiles.