Funciones, control de ayuda y argumentos

Una de las funciones mas importantes que tenemos que añadir a nuestros scripts es la presencia de un menu de ayuda, que sea accesible usando las opciones -h o --help y cuando el usuario escriba el nombre del script pero sin introducir los argumentos necesarios o de forma incorrecta. Esta norma se aplica también a nuestros propios scripts, aquellos que solo vamos a usar nosotros y nos vamos a compartir en principio con nadie mas. y direis ¿Por que? si lo he escrito yo, se perfectectamente como funciona. Pues si ahora sabes como funciona, pero te invito a revisitar tus scripts tras tres meses un año o incluso mas de un año, ya me contarás si ves tran claro como funcionaba. Por supuesto, tras un ratito (minutos) volveras a recordar como funciona, pero escribiendo el nombre del script y la opcion -h lo verás en 5 segundos. Recuerda que ¡el tiempo es oro!.


Antes de comenzar con las funciones y las ayudas no estaría mal recordar algunas de las variables especiales que ya vimos en el tema de introducción a bash scripting y alguna nueva que vamos a necesitar para controlar los parámetros de entrada en un script:


  • $1$9 los argumentos que podemos pasar a un script en Bash
  • $0 que se reserva para el nombre del script
  • $# el número de argumentos que se pasan a un script
  • $@ todos los argumentos que se han pasado al script durante la ejecución (podemos iterar sobre ella)



Funciones


Una función podría definirse como un módulo dentro de un programa o script que realizará una o mas funciones, aunque lo ideal es asociar una sola tarea perfectamente definida a una función. Es decir, una función no es mas que parte del código de script contenido dentro de un módulo que tiene una entrada(parámetros de entrada) y una salida (el valor de retorno).


Como en cualquier lenguaje de programación, en Bash también se pueden crear funciones para ordenar y modular el código y hacer los scripts más eficientes.


imaginemos que una de las tareas que vamos a llevar siempre a cabo en nuestro dia a dia al escribir código en el laboratorio es leer archivos fasta y necesitamos quedarnos solo con las secuencias y eliminar los headers. Nuestro script tendría que tener el siguiente código para ello:

input_file=$1
grep -v "^>" $input_file

considerando el siguiente archivos fasta.fa:

>seq1
agtcgatgctagtc
>seq2
gtgctagtcgta

al ejecutar el codigo de arriba obtendríamos:

~bash script.sh
agtcgatgctagtc

gtgctagtcgta

Para convertir dicho código en una función en Bash que se llame get_fasta_lines solo tendríamos que hacer lo siguiente:

function get_fasta_lines {
  input_file=$1
  grep -v "^>" $input_file
}

Esto que acabamos de hacer se denomina declarar una función, que no es mas que crear una función con un código de ejecución en su interior. Ahora esta función esta disponible dentro del script en el que la hemos declarado y la podremos usar siempre que queramos. Para usar una función solo tenemos que hacer lo siguiente:

#!/bin/bash

function get_fasta_lines {
  input_file=$1
  grep -v "^>" $input_file
}

get_fasta_lines fasta.fa

Si guardamos este codigo de nuevo en nuestro script, su ejecución nos mostrará lo siguiente:

~bash script.sh
agtcgatgctagtc

gtgctagtcgta

Fijaros la función tiene su propia entrada de parámetros, en este caso la variable input_file será el primer parámetro que le pongamos a la función, en este caso el archivo fasta.fa


podemos pasar todos los argumentos que queramos a una función y usar todas las variables especiales mostradas al inicio del tema $1-$9, $0, $@ y $#.


por ejemplo una función que sume dos digitos, usaría dos argumentos:

function suma(){
total=$(($1+$2))
echo $total
}

Si ejecutas ahora suma 1 2 veras que el resultado que obtendrás sera 3.



Por ultimo como añadido, comentar que tambien se puede declarar las funcione sin añadir la palabra function al principio.



Mostrar ayuda en un script


normalmente la ayuda en cualquier script la podemos obtener escribiendo el nombre del script seguido de la opción -h o –help. Veamos como generar esta función a un script con el bucle for.

for arg in "$@" #en todos los argumentos que han entrado
do
    if [ "$arg" == "--help" ] || [ "$arg" == "-h" ] 
    then
        echo "Esta es la ayuda del script"
    fi
done

Que fácil!. Ahora ya tan solo es cuestión de rellenar la ayuda y explicar cual es la funcion del script, para que sirve y cuales son las opciones que tiene y los formatos de datos de entrada y salida. Eso si hay que ser conciso y muy claro, nada de enrollarse.


Podemos crear una función sencilla donde almacenar toda la información de ayuda,

function ayuda(){
    echo "Este script se utiliza para algo chachi"
    echo
    echo "Usage: script.sh [-op1 opcion1] [-op2 opcion2]"
    echo
    echo "-op1 es la opcion numero 1"
    echo "-op2 es la opción número 2"
    echo
}

entonces ahora el codigo del script quedaria de la siguiente manera:

#!/bin/bash

function ayuda(){
    echo "Este script se utiliza para algo chachi"
    echo
    echo "Usage: script.sh [-op1 opcion1] [-op2 opcion2]"
    echo
    echo "-op1 es la opcion numero 1"
    echo "-op2 es la opción número 2"
    echo
}


for arg in "$@" #en todos los argumentos que han entrado
do
    if [ "$arg" == "--help" ] || [ "$arg" == "-h" ] 
    then
        ayuda
    fi
done



Control de argumentos


bien ahora volvamos al script del inicio get_fasta_lines.sh, vamos a introducir en dicho script una funcion de ayuda y codigo para controlar la entrada de argumentos.

Lo primero que vamos a hacer es añadir una sentencia if-then-elif-else para controlar los parámetros (nota: tambien podríamos hacerlos con case-esac). Este script en principio solo necesita un único parámetro para funcionar, el archivo fasta. Que tal si añadimos un control de argumentos que detecte si el usuario ha introducido al menos un argumento y que dicho argumento se corresponde con un archivo que existe. ¡Vamos a ello!

#!/bin/bash

# funcion que ejecuta el core del script
function get_fasta_lines {
  input_file=$1
  grep -v "^>" $input_file
}


# función con la información de la ayuda
function ayuda {
    echo " get_fasta_lines: muestra solo las cadenas de nucleotido/aminoacidos de un archivo fasta"
    echo
    echo "    Usage: get_fasta_lines.sh [-in fasta input]"
    echo
    echo "    -in/--input archivo multifasta de entrada"
    echo 
}

# control de argumentos
if [ $# -lt 1 ]
    then
        echo "error: no se ha introducido ningun argumento"
        echo
        ayuda
elif [ -e $1 ]
    then
        get_fasta_lines $1
elif [ "$1" == "--help" ] || [ "$1" == "-h" ] 
    then
        ayuda
else
    echo "error: argumento no valido"
    echo
    ayuda
fi

Como podeis ver, hemos ordenado el codigo haciendo uso de funciones. En este caso hemos almacenado el codigo principal del script en la función get_fasta_line(). Por otro lado, hemos generado una función con el mensaje de ayuda, funcion ayuda(). Por ultimo el chequeo de argumentos y la ejecución del script ha quedado bajo el control de una sentencia if-then-elif-else.


veamos que salidas muestra el script en función de los argumentos que utilicemos:

si no introducimos ningun argumento:

~$  bash get_fasta_lines_2.sh 
error: no se ha introducido ningun argumento

 get_fasta_lines: muestra solo las cadenas de nucleotido/aminoacidos de un archivo fasta

    Usage: get_fasta_lines.sh [-in fasta input]

    -in/--input archivo multifasta de entrada

ahora si introducimos un argumento no valido, por ejemplo “1”

~$  bash get_fasta_lines_2.sh 1
error: argumento no valido

 get_fasta_lines: muestra solo las cadenas de nucleotido/aminoacidos de un archivo fasta

    Usage: get_fasta_lines.sh [-in fasta input]

    -in/--input archivo multifasta de entrada

y que pasa si utilizamos como argumento -h o –help:

~$  bash get_fasta_lines_2.sh -h

 get_fasta_lines: muestra solo las cadenas de nucleotido/aminoacidos de un archivo fasta

    Usage: get_fasta_lines.sh [-in fasta input]

    -in/--input archivo multifasta de entrada

Ahora te toca a ti, que tal si pruebas a meter mas argumentos y sus correspondientes controles, experimenta con case-esac, argumentos opcionales y la función shift.