Publi

Cómo saber si existe una función en Bash, saber qué estamos ejecutando y más

photo-1457317680121-ef12e98979e8

En nuestra historia como programadores, hacemos infinidad de llamadas a funciones propias, de terceros, de biblioteca, a otros programas a un alias, etc Y si queremos que nuestros scripts sean lo más robustos posible, debemos tener claro antes de la ejecución que aquello que llamamos se puede ejecutar o dar opciones si algún comando o función no existe.

Por ejemplo, nuestro script necesita hacer una llamada a wget de este tipo:

wget -O salida http://totaki.com

Pero queremos hacer que sea compatible con otros sistemas, por ejemplo, con cURL, y no podemos cambiar uno por otro, pues con curl sería con o minúscula:

curl -o salida http://totaki.com

Nuestro script podrá ser:

1
2
3
4
5
6
7
8
if [ -n "$(which wget)" ]; then
    wget -O salida http://totaki.com
elif [ -n "$(which curl)" ]; then
    curl -o salida http://totaki.com
else
    echo "No puedo descargar el fichero"
    exit 1
fi

De modo que probamos la existencia de diferentes programas y utilizamos el que tengamos a disposición. Otra forma sería crear una función bash que se llame wget, pero que utilice cURL para trabajar:

1
2
3
4
5
6
7
8
if [ -z "$(which wget)" ]; then
   function wget()
   {
      curl -o "$2" "$3"
   }
fi

wget -O salida http://totaki.com/

Aunque sólo funcionará cuando las llamadas a wget sean siempre de la misma forma. Ahora bien, hemos utilizado which para conocer dónde se ubica el archivo ejecutable de dicho comando, pero, ¿ y si el comando que estamos utilizando es un alias, o una función de bash ? en ese caso which serviría de poco, ya que éste suele ser un programa externo (en casi todos los Unix), y siendo un programa externo no podría acceder fácilmente a objetos almacenados en Bash.

Para ello, Bash proporciona una orden interna llamada type, que es capaz de decirnos de qué se trata el comando que le pasamos como argumento. Por ejemplo:

$ type wget
wget is /usr/bin/wget
$ type sudo
sudo is /usr/bin/sudo
$ type for
for es una palabra clave del shell
$ function funci() { echo “Hello World!”; }
$ type funci
funci: es una función
funci ()
{
echo “Hello World!”
}
$ type time
time es una palabra clave del shell
$ type echo
echo es una orden interna del shell
$ type ls
ls es un alias de `ls –color=auto’
$ type type
type es una orden interna del shell
$ type algoquenoexiste
bash: type: algoquenoexiste: no se encontró

Estos son algunos ejemplos. Como curiosidad, tenemos time, que es palabra clave de shell, interno de Bash, aunque también existe un comando time que debe ser llamado explícitamente /usr/bin/time; si sólo llamamos a time, Bash prefiere llamar primero a la versión interna, le pilla más cerca.
Vemos también que palabras como for, if, while, case, do, etc también son detectadas por type. Es más, type, se autodefine como orden interna.
Por otro lado, en distribuciones como Ubuntu y derivadas, encontramos que ls es un alias de ls –color=auto para que todo se vea un poco más bonito

¿cómo utilizar esto en nuestros scripts

Pero, ¿cómo utilizamos esto en nuestros scripts? La salida de type es algo compleja para tener que analizarla con métodos textuales, y como vemos, está en español, por lo que estará traducida a decenas de idiomas y eso no es bueno a la hora de llevarnos los scripts a otros lugares. Afortunadamente, type tiene un modificador, -t, que nos devuelve palabras sencillas en inglés (sea cual sea el idioma que utilicemos) que sí podremos utilizar en nuestros scripts:

$ type -t wget
file
$ type -t sudo
file
$ type -t for
keyword
$ type -t funci
function
$ type -t time
keyword
$ type -t echo
builtin
$ type -t ls
alias
$ type -t type
builtin
$ type -t algoquenoexiste

Vemos que en el último caso no recibimos respuesta, es decir, está vacía.

Y con este modificador podremos hacer algo como:

1
2
3
if [ -n "$(type wget)" ]; then
  echo "Existe wget, ya sea aplicación, alias, función..."
fi

Porque si no tenemos wget instalado, tenemos la función anterior creada y utilizamos which, éste seguirá dando un negativo.

Aunque también podemos, como dice el título, verificar la existencia de funciones, algo así como un typeof de Javascript:

1
2
3
if [ "$(type mifuncion)" = "function" ]; then
   echo "La función existe";
fi

o

1
2
3
if [ -z "$(type mifuncion)" ]; then
   echo "La función NO existe";
fi

Y esto podríamos utilizarlo cuando incluimos scripts, a modo de plugin de nuestro script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for plugin in plugins/*.sh; do
  \. $plugin
  plugin_name=${plugin%.*}

  if [ -z "$(type $plugin_name"_version")" ]; then
    echo "Plugin $plugin_name no válido"
    exit 1
  fi

  if [ "$(type $plugin_name"_init")" = "function" ]; then
    echo "Inicializando plugin $plugin_name"
    $plugin_name"_init"
  fi
done

Con este pequeño script incorporaríamos todos los archivos .sh que estén dentro del directorio plugins, tras ello, buscaríamos las funciones cuyos nombres son: “nombre_del_archivo_version” y “nombre_del_archivo_init”. Los nombres del archivo van sin extensión. La primera función, si no la encontramos, concluiremos el programa diciendo que el plugin no es válido; la segunda hará una ejecución a dicha función si existe, porque el plugin tal vez no tenga que ser inicializado.

Foto: Matthew Kane

También podría interesarte...

Leave a Reply