Hoy voy a explicar los conceptos básicos para hacer un script sofisticado y flexible que permita ordenar el código, permitir que vaya creciendo sin desordenarse, y hacer código reutilizable en futuros scripts.
Para conseguir esto, os explico como usar funciones y como procesar opciones para realizar distintas tareas con un mismo script. El concepto USAGE es básico en scripting, el propio script te dice como utilizarlo gracias a la utilización de opciones al invocarlo.
Todo el código del script lo teneis como
snippet en mi repositorio público. Si lo ponemos en un fichero con permisos de ejecución y lo lanzamos obtenemos:
./mySecondScript.sh
------- My second script begins execution...
--------------------------------------------
Executing command: mySecondScript.sh
At path: /home/piperoman/work/git/bash
Evaluation options, number of arguments: 0
Options: --
------- My second script end now!
---------------------------------
Lo único que nos indica el script es algo de información básica, su nombre, donde se ejecuta, cuantas opciones introducimos y cuales son. En este caso no hemos metido ninguna. Las opciones o argumentos, se meten añadiendo un espacio en blanco y escribiendo algo. Nuestro script debe estar preparado para interpretar la entrada. Si además puede detectar errores y guiar al usuario, mejor.
Probamos a añadir la opción --help (-h también valdría) y añade algo de información:
./mySecondScript.sh --help
...
--------------------------------------------------
| This function teach you how this script works
| Usage:
| ./mySecondScript.sh
| -h|--help = Print usage
| -s|--say = Print echo with message
| -o|--options = Print all options
| -a|--author = Print author's name
--------------------------------------------------
...
Interesante, vemos 4 opciones, pero... ¿Cómo funciona? En primer lugar, explicamos las funciones. Podemos definir una función en cualquier parte del script con esta sintaxis:
nombre() { código } . Por convenio (propio, vamos que me da la gana) todos los nombres de funciones comenzarán con f_nombre, veamos un ejemplo de este script:
f_display_usage()
{
echo -e "\n--------------------------------------------------"
echo "| This function teach you how this script works"
echo -e "| Usage:\n| $0" # -e option allow use \n
echo "| ${optionHelp} = Print usage"
echo "| ${optionSay} = Print echo with message"
echo "| ${optionPrintOptions} = Print all options"
echo "| ${optionAuthor} = Print author's name"
echo -e "--------------------------------------------------"
}
Para ejecutar el código contenido en la función, basta con invocarla escribiendo su nombre, en este caso se ejecuta si se introduce la opción help:
-h|--help) f_display_usage ;; # Call function
Debes tener presente que al llamar una función, se puede hacer con argumentos, pero dentro de la función no podrás acceder a los anteriores, se resetean. Por esta razón, en este script se guardan los argumentos al comiendo por si se necesitan más adelante.
args=(${@}) # Arguments
Nargs=(${#}) # Number of arguments
Además, asi podemos manipular datos pasando argumentos a las funciones. Sin llamar a ninguna función, tenemos los argumentos en las variables ${0}, ${1}... Siendo la 0 el nombre del programa, y el resto los argumentos introducidos. Para leerlos utilizaremos un bucle:
while [ "$1" != "" ]
do
echo "Processing option: ${1}"
case "$1" in
# Process option
esac
shift; # Advance
done;
Mientras el primer argumento no sea vacio, los leemos. ¿Cómo puede funcionar? Cada iteración miramos el contenido del argumento 1, al procesarlo, usamos shift, este comando elimina el argumento 1 y corre a la izquierda los restantes, quedando en la posición 1 el 2 hasta el N en la posición N-1. Lo hago así por comodidad.
Para aprender a procesar os enseño 2 ejemplos concretos:
-h|--help) f_display_usage ;; # Call function
-s|--say) f_say $2 ; shift ;; # Call function with argument, and discard argument
En el primer caso, pedir ayuda, solo se llama la función que explica como se usa. En el caso de usar la opción de decir, utiliza el siguiente argumento para decirlo (asumiendo que el usuario lo ha introducido bien, printeará lo que haya) y corre una posición extra los argumentos para no usar ese argumento como opción. Este formato de usage permite evaluar todas las opciones que se den al script, aunque no analiza que las opciones extra son lo que espera. Veamoslo:
./mySecondScript.sh -a --say hola
...
Processing option: -a
--------------------------------------------------
The author is: vgoni
--------------------------------------------------
Processing option: --say
--------------------------------------------------
I want to say: hola
--------------------------------------------------
...
Como podeis ver, utilizamos la opción a, es decir, author, y la opción say con el texto hola. Si has llegado hasta aquí, espero que te estes preguntando ¿Y como funciona la lectura de las opciones? Magia no es. Efectivamente, he usado un truco que explico ahora. Antes del bucle para procesar las opciones, uso el comando getopt para evaluar las opciones que espero como argumentos del comando y guardo el resultado en la variable options:
options=$(getopt -o hs:ao -l help,say:,author,options -- "${@}")
Lo hago antes de llamar a ninguna función porque sino perderíamos los argumentos, como ya he comentado antes. Tienes más información del comando
aquí. Básicamente la opción -o indica las opciones validas de 1 caracter, y la opción -l los nombres completos. Si detrás de alguno está el caracter : significa que usa un argumento. Para las opciones help, say con un argumento, autor y opciones, queda como arriba. Después, invoco la función para evaluar la variable, y eso hace el truco.
f_evaluate_options()
{
echo " Evaluation options, number of arguments: ${Nargs}"
eval set -- ${options}
echo " Options: ${options}"
}
Con todo el rollo que he metido, te dejo que revises tu el script completo e intentes comprender como funciona todo, para que veas puedas hacer tus propios scripts molones en el futuro.