Aprende GNU/Linux 0

Aprendiendo shell scripting en Linux: Depuración

En el desarrollo de nuestros Shell scripts, nos podemos encontrar con errores que pueden darnos un buen dolor de cabeza si el script en cuestión es largo y complejo. Es algo muy típico y necesario en el mundo de la programación disponer de herramientas que nos ayudan a localizar y obtener información sobre posibles errores. Estas herramientas son las denominadas depuradores o, en la lengua de Shakespeare, debuggers.

En el caso de Bash, también disponemos de un depurador que nos va a ayudar con este cometido. En esta entrada voy a explicar su funcionamiento y voy a exponer algunos ejemplos para que quede claro su funcionamiento.

El bash debugger

Para empezar a entender este depurador es importante saber que dispone de muchísimas opciones, pero en este artículo me voy a centrar en las que considero más importantes a la hora de depurar nuestros amados scripts.

Para depurar tenemos dos comandos, que se pueden usar de forma independiente, o en combinación:

Modo Descripción
set -v Modo verbose, imprime las instrucciones que se realizan.
set -x Instrucciones y sus argumentos cuando son ejecutados ( con indicador de anidación )
set -xv Combinación de ambas
Para mejorar la comprensión del artículo, solamente voy a utilizar la opción set -x. Simplemente porque esta opción es la que normalmente mejor nos va a indicar el funcionamiento del script. Utilizar las otras dos opciones seria muy similar. 
¡Atención!

Estas opciones las podemos invocar desde dentro del script, o desde fuera. La invocación desde fuera es mejor opción en el caso que queramos depurar el script completo, por el simple hecho de que no tendremos que tocar nada en el código. Lo haríamos de la siguiente manera:

bash -x nombre_script.sh

Donde nombre_script.sh es nuestro script con los permisos de ejecución pertinentes.

Para observar de forma más detenida su funcionamiento, vamos a utilizar un ejemplo, que como casi siempre en la programación, es la mejor manera de ver las cosas. Como ejemplo vamos a utilizar el siguiente script:

#! /bin/bash

cont=5
until [ $cont -lt 1 ];
do
     let cont=cont-1 
done
echo $cont

Es un script muy sencillo que cuenta desde cinco hasta uno mediante un bucle. Una vez la variable de control llega a uno, el bucle termina e imprime la variable del contador. El resultado de su ejecución seria:

$ bash prueba.sh
0

Como vemos el resultado del script es 0, cuando siguiendo la lógica, debería ser 1. Ahora veamos cual seria el resultado si ejecutamos el script con el depurador:

bash -x funciones.sh
+ cont=5
+ '[' 5 -lt 1 ']'
+ let cont=cont-1
+ '[' 4 -lt 1 ']'
+ let cont=cont-1
+ '[' 3 -lt 1 ']'
+ let cont=cont-1
+ '[' 2 -lt 1 ']'
+ let cont=cont-1
+ '[' 1 -lt 1 ']'
+ let cont=cont-1
+ '[' 0 -lt 1 ']'
+ echo 0
0

Podemos observar como el depurador nos imprime cada linea de la secuencia de ejecución del script. Como este script en particular utiliza un bucle, se puede ver como imprime cada iteración y como el valor de la variable cont decrementa. Una vez el bucle termina, se ejecuta echo $cont ( echo 0 en el depurador ) y se imprime su resultado.

Con el depurador podemos ver porque el ultimo echo nos devuelve un 0, en vez del 1 que podríamos esperar ( teniendo en cuenta la condición).  El motivo es sencillo, la condición es que la variable sea igual a uno y en la ultima comparación, el decremento se ejecuta de nuevo. Si queremos que el ultimo valor sea uno, será necesario cambiar la condición para que se detenga en el dos o buscar otro tipo de bucle. Evidentemente es un ejemplo muy trivial e incluso tonto, pero sirve para ver como funciona el depurador y sus posibles utilidades.

Las anidaciones

Como hemos podido observar hasta ahora, por cada linea que nos muestra el depurador ( quitando resultados de la salida estándar como los del echo )  nos añade al principio el símbolo +. Esto corresponde al nivel de anidación de la instrucción, por lo que, dependiendo la cantidad de símbolos + que tiene una instrucción, esta pertenecerá a un nivel de anidación diferente. Veamos un ejemplo para entenderlo mejor:

#!/bin/bash
# Declaramos la función factorial  
function factorial() 
{ 
   if (( $1 < 2 ))
   then
     echo 1
   else
     # Aquí realizamos la recursividad llamando a factorial desde factorial
     echo $(( $1 * $(factorial $(( $1 - 1 ))) ))
   fi
}

# Llamamos a la función 
factorial $1

Este script recursivo calcula el factorial de un numero ( que pasamos como argumento en la llamada )  mediante una función llamada factorial(). Si ejecutamos este script con el depurador el resultado es el siguiente:

bash -x script.sh 5

+ factorial 5
+ ((  5 < 2  ))
++ factorial 4
++ ((  4 < 2  ))
+++ factorial 3
+++ ((  3 < 2  ))
++++ factorial 2
++++ ((  2 < 2  ))
+++++ factorial 1
+++++ ((  1 < 2  ))
+++++ echo 1
++++ echo 2
+++ echo 6
++ echo 24
+ echo 120
120

Podemos observar como el nivel de anidación cambia por cada llamada recursiva y en este ejemplo gracias al depurador podemos ver el resultado del factorial de cada una de las llamas a la función. Por ejemplo, en el nivel de anidación 3 ( +++ ), tenemos la llamada a factorial 3, que más abajo podemos ver otra vez en el nivel de anidación 3 ( +++ ) es 6.

Breakpoints

Con lo que hemos hablado hasta ahora, solo podríamos realizar un depurado completo del script. Si nuestro script tiene muchas lineas , puede ser un autentico martirio buscar dentro de tantas lineas y anidaciones el error. Para solucionar esto, en el mundo de la programación y la depuración, se utilizan los breakpoints. Con los breakpoints podemos especificar puntos dentro de nuestro código donde queremos que el depurador actué. De esta forma, y ya hablando dentro de bash, el depurador sólo nos mostrará por la salida estándar el trozo que le indiquemos. Para indicar el código que queremos depurar, colocamos este código entre set-x y set+x ( o -v / -xv ). Este método corresponde a la depuración desde dentro del script de la que hablé al principio.

Veamos un ejemplo:

#! /bin/bash 
echo Empecemos...
echo Este script solo entiende el numero 1 y el 2, así que tu sabrás...
echo Introduce un numero:
read a
set -x
  case $a in
     1)
        echo "Tu numero es el uno"
     ;;
     2)
        echo "Tu numero es el dos"
     ;;
     *)
        echo "El que avisa, no es traidor.. ni idea que numero es ese :("
     ;;
  esac
set +x

En este caso, el depurador sólo nos mostraría la depuración del case, obviando los echo, simplificando la comprensión del resultado. Es importante tener esto en cuenta, porque obtener información del funcionamiento de nuestro script es útil, pero el exceso de información puede dificultar la compresión. Utilizar estos breakpoints en aquellos puntos en los que creemos que el script puede fallar, volverá la tarea de corrección mucho más eficiente.

Guardando el resultado

El resultado del depurador se entrega por la salida estándar, que como ya vimos en este artículo, por defecto, es la pantalla. Si queremos guardar el resultado del depurador para su posterior análisis, es tan sencillo como añadir estas dos lineas al principio de nuestro script:

exec 5> debug_resultado.txt
BASH_XTRACEFD="5"

y si queremos hacerlo sin añadir nada a nuestro script:

bash -v nombre_script.sh > debug_resultado.txt 2>&1

En ambos caso se creara un fichero debug_resultado.txt con el resultado del depurador en el directorio del script.

Tips adicionales

Algunas consideraciones extras sobre el depurador de Bash:

  • Es posible usar la depuración añadiendo -x ( o -v/-xv ) a la linea donde se indica el intérprete.

Ejemplo:

#! /bin/bash -x
...
  • Recordad que, aunque usar el depurador es mucho mas sofisticado, nunca fallara el método de añadir echos en todas partes para que nos indiquen como anda la cosa, y las vueltas que da cada bucle .
  • Se puede utilizar -n para comprobar si existen errores sintácticos.
  • Existen desarrollos de debuggers algo más vistosos independientes a GNU como puede ser: www.shellcheck.net

Espero que os sea útil, cualquier duda en los comentarios, y aquí os dejo la lista completa de los artículos escritos hasta la fecha sobre Bash scripting y la linea de comandos:

 

Sobre el autor / 

AsierPH

Entusiasta de las tecnologías libres y fundador de OvToaster.com | “Las obras de conocimiento deben ser libres, no hay excusas para que no sea así“

Articulos relacionados

Deja tu comentario

Tu correo no sera publicado. Los campos requeridos están marcados *