En este momento estás viendo Depurar código en bash-script

Depurar código en bash-script

Depurar código en bash-script

Para un administrador de sistemas, es necesario el uso de scripts de shell para realizar su trabajo más eficientemente. Vemos como depurar el código bash-script en nuestra terminal.

Sumario

  • Que es un script
  • Depuración del código en bash-script
  • Depuración del código completo
  • Modificando la primera linea del script
  • Indicando la depuración en la orden de ejecución
  • La opción -e
  • La opción -v
  • Depuración por partes del código
  • Ejecución paso a paso

Que es un script

Los scripts de shell son una herramienta de ejecución de comandos reutilizables, que se guardan en un fichero de texto ejecutable. Para crear este fichero ejecutable puedes utilizar cualquier editor de texto. Estos prácticos scripts hacen de los sistemas GNU/Linux uno de los sistemas operativos más flexibles de los disponibles.

Los scripts comienzan con el «shebang», una expresión compuesta por un conjunto de caracteres, una almohadilla y un final de exclamación «#!» y a continuación la ruta absoluta a intérprete de comandos que se debe llamar, por ejemplo «#!/bin/bash».

Es muy recomendable añadir comentarios al código para explicarlo, lo que nos ayudará en las modificaciones que precise así como también ayudará a comprenderlo a terceros.

No obstante, cuando estamos «picando código», no siempre salen las cosas como esperamos y tenemos que determinar que es lo que falla en la ejecución del script. Cuando nos encontramos en esta situación, nos viene muy bien tener opciones de depuración del código.

Para esta tarea podemos usar las opciones de depuración del shell que nos permiten hacerlo, tanto de manera parcial o analizando todo el código.

Lo más habitual es ejecutar el script con la opción «-x», es decir, en modo de depuración. Nos mostrará en la salida estandar cada comando y sus argumentos, después de que los comandos se hayan expandido pero antes de que se ejecuten. En la salida no veremos los comentarios añadidos al código.

Depuración del código en bash-script

Como hemos comentado, podemos depurar todo el código del programa a la vez o por partes. Vemos como hacerlo.

Depuración del código completo.

Tanto en bash como en sh, la depuración es muy importante ya que su tipado débil, puede provocar errores difíciles de encontrar, como por ejemplo el valor o el nombre de una variable mal introducida.

Para ello tenemos la opción «set -u». Colocando esta sentencia al principio del script, el intérprete nos devolverá un error cuando se llame a una variable a la que no hayamos asignado un valor. Veamos.

Tenemos un script llamado «variable.sh» al que hemos dado permisos de ejecución con «chmod +x variable.sh», con el siguiente contenido.

#!/bin/bash

val=1
echo $(expr $val + 1)
Script variable.sh
Script variable.sh

Al ejecutarlo nos da la salida

javier@debian:~/Pruebas$ ./variable.sh 
2
javier@debian:~/Pruebas$
Ejecución de variable.sh
Ejecución de variable.sh

Modificamos el código equivocándonos en la variable

#!/bin/bash
val=1
echo $(expr $var + 1)
Error en la llamada a la variable
Error en la llamada a la variable

Y en la salida, bash no nos dice el error

javier@debian:~/Pruebas$ ./variable.sh 
1
javier@debian:~/Pruebas$
No obtenemos información del error
No obtenemos información del error

Si añadimos «set -u» al código

#!/bin/bash
set -u
val=1
echo $(expr $var + 1)
Añadimos set -u al código
Añadimos set -u al código
javier@debian:~/Pruebas$ ./variable.sh
./variable.sh: línea 4: var: variable sin asignar
javier@debian:~/Pruebas$
Nos avisa de la variable sin asignar
Nos avisa de la variable sin asignar

Si utilizamos «set +u», se revierte el efecto.

Pero el error puede que no esté en las variables. Con la opción «-x» (xtrace), depuraremos todo el código. Tenemos dos opciones.

Modificando la primera linea del script

Para utilizar este método, tienes que modificar la primera linea del script, el «shebang», con la opción de depuración. La opción «-x» ordena imprimir, por la salida estandar, cada una de las instrucciones que se ejecutan en el script, permitiéndonos seguir, de forma sencilla, la ejecución del script.

#!/bin/bash -x 

Podemos utilizar las opciones adicionales siguientes:

  • -e Indica al intérprete que cancele la ejecución del programa nada más ejecutar una orden que devuelva error (es decir, un valor distinto de 0).
  • -v (verbose) Imprime por la salida estandar todas las órdenes que se ejecutan. Así veremos cual falla.

Lo vemos con un ejemplo sencillo. Creamos un script al que llamamos «script.sh», una calculadora sencilla y le damos permisos de ejecución.

nano script.sh

Copia y pega el siguiente código

#/bin/bash
#calculadora

clear
sum=1
i="y"
echo "Vamos a realizar una operacion aritmetica
echo
echo
        sleep 2
        echo "Introduce la primera cifra"
read n1
        echo "Introduce la segunda cifra"
read n2
while [ $i = "y" ]
        do
        echo "1.Sumar"
        echo "2.Restar"
        echo "3.Multiplicar"
        echo "4.Dividir"
        echo "Introduce el numero de operacion deseada (1 al 4)"
read ch
case $ch in
        1)sum=$(expr $n1 + $n2
        echo " Resultado de la suma ="$sum;;
        2)sum=$(expr $n1 - $n2)
        echo " Resultado de la resta ="$sum;;
        3)sum=$(expr $n1 \* $n2)
        echo " Resultado de la multiplicacion ="$sum;;
        4)sum=$(expr $n1 / $n2)
        echo " Resultado de la division ="$sum;;
        *)echo "Tu seleccion es incorrecta";;
esac
echo  "Quieres continuar (y/n) ?"
read i
if [ $i != "y" ]
        then
        echo "Hemos terminado" $USER
        exit
fi
done
Script de una calculadora sencilla
Script de una calculadora sencilla

Damos permiso de ejecución

chmod +x script.sh

Al ejecutarlo nos da un error. Faltan unas comillas dobles en la linea 21.

./script.sh: línea 21: error sintáctico cerca del elemento inesperado `('
./script.sh: línea 21: `	echo "Introduce el numero de operacion deseada (1 al 4)"'
Error en ejecución de calculadora.sh
Error en ejecución de calculadora.sh

Si añadimos la opción «-x» al shebang

#!/bin/bash -x
#calculadora
...
Añadimos la opción -x al shebang
Añadimos la opción -x al shebang

Fíjate que en la primera línea he incluido la opción -x. Si ahora ejecutas el script con «bash script.sh», verás lo siguiente,

+ sum=1
+ i=y
./script.sh: línea 21: error sintáctico cerca del elemento inesperado `('
./script.sh: línea 21: `	echo "Introduce el numero de operacion deseada (1 al 4)"'
Salida con la opción de depuración
Salida con la opción de depuración

Antes de ejecutarse la primera línea del script la imprime. De esta manera sabemos exactamente lo que ha ejecutado. Para distinguirlo incluye un «+» al comienzo de la línea.

Indicando la depuración en la orden de ejecución

Otra forma de ejecutar la depuración es indicando la opción en la misma orden de ejecución del script, en vez de incluirla en el código. Lo haremos con la siguiente sintaxis y el mismo resultado

bash -x script.sh

Y obtenemos la misma salida que antes

+ sum=1
+ i=y
script.sh: línea 21: error sintáctico cerca del elemento inesperado `('
script.sh: línea 21: `	echo "Introduce el numero de operacion deseada (1 al 4)"'

La opción -e

Disponemos de dos opciones que hemos comentado antes «-e» y «-v». La opción «-e» conseguirá que cuando detecte un error, es decir, devuelva un valor distinto de «0», trunque la ejecución y salga. Lo que nos permitirá determinar donde esta el fallo. Lo vemos con un ejemplo, primero sin la opción y luego con ella. Creamos un fichero con el siguiente contenido. Es un script que nos dirá si el argumento es numérico, alfabético o alfanumérico.

nano numero.sh
#!/bin/bash
if test $# -ne 1
        then echo "debe haber un argumento"
        exit 1
fi
numero=$(echo $1 | grep "^[0-9][0-9]*$")
letra=$(echo $1 | grep "^[a-zA-Z][a-zA-Z]*$")
if [ $numero ]
        then echo $1 es un valor numérico"
elif [ $letra ]
        then echo "$1 es una valor alfabético"
else
        echo "$1 es un valor alfanumérico"
        exit 2
fi
exit 0
Contenido del script numero.sh
Contenido del script numero.sh

Le damos permisos de ejecución con «chmod +x numero.sh».

Al ejecutar el script con la opción «-x» sola, obtenemos la siguiente salida

bash -x numero.sh 123
javier@debian:~/Pruebas$ bash -x numero.sh 123
+ test 1 -ne 1
++ echo 123
++ grep '^[0-9][0-9]*$'
+ numero=123
++ echo 123
++ grep '^[a-zA-Z][a-zA-Z]*$'
+ letra=
numero.sh: línea 13: EOF inesperado mientras se buscaba un `"' coincidente
numero.sh: línea 17: error sintáctico: no se esperaba el final del fichero
javier@debian:~/Pruebas$ 

Ahora vemos las diferencias al ejecutar el script con las opciónes «-xe».

bash -xe numero.sh 123
javier@debian:~/Pruebas$ bash -xe numero.sh 123
+ test 1 -ne 1
++ echo 123
++ grep '^[0-9][0-9]*$'
+ numero=123
++ echo 123
++ grep '^[a-zA-Z][a-zA-Z]*$'
+ letra=
javier@debian:~/Pruebas$ 
Depuración con la opción -xe
Depuración con la opción -xe

Como ves al ejecutar la linea «echo $1 es un valor numérico» y faltar las comillas dobles iniciales, termina la ejecución del script. Ya hemos localizado el error.

Las opciones las puedes utilizar juntas o separadas.

bash -x -e script.sh
#o 
bash -xe script.sh
#o 
bash -ex script.sh

El resultado será el mismo.

La opción -v

La opción «-x», como hemos visto, expande los valores, pero si utilizamos la opción «-v», nos imprimirá todas las líneas en el orden que las vaya leyendo, sin expandirlas.

Si ejecutamos el último ejemplo que hemos visto «bash -xv numero.sh 123», el resultado será el siguiente,

javier@debian:~/Pruebas$ bash -xv numero.sh 123

#!/bin/bash
if test $# -ne 1
	then echo "debe haber un argumento"
	exit 1
fi
+ test 1 -ne 1
numero=$(echo $1 | grep "^[0-9][0-9]*$")
++ echo 123
++ grep '^[0-9][0-9]*$'
+ numero=123
letra=$(echo $1 | grep "^[a-zA-Z][a-zA-Z]*$")
++ echo 123
++ grep '^[a-zA-Z][a-zA-Z]*$'
+ letra=
if [ $numero ]
	then echo $1 es un valor numérico"
elif [ $letra ]
	then echo "$1 es una valor alfabético"
else
	echo "$1 es un valor alfanumérico"
	exit 2
fi
exit 0
numero.sh: línea 13: EOF inesperado mientras se buscaba un «"» coincidente
numero.sh: línea 17: error sintáctico: no se esperaba el final del archivo
javier@debian:~/Pruebas$ 
Opciones -xv de depuración
Opciones -xv de depuración

De esta forma podemos repasar todas las lineas de código del script y ver como es tratada por el intérprete.

Depuración por partes del código

Todo esto vendrá muy bien cuando tenemos poco código que depurar, pero también podemos depurarlo por partes, estableciendo unos cortes. Para ello utilizamos la herramienta «set» con las opciones «-x», para iniciar el corte y «+x» para terminarlo. Lo vemos con un ejemplo. Modificamos el código para incluir el corte,

#!/bin/bash
if test $# -ne 1
        then echo "debe haber un argumento"
        exit 1
else
	echo "El argumento es correcto"
fi
set -x      # Iniciamos el corte
numero=$(echo $1 | grep "^[0-9][0-9]*$")
letra=$(echo $1 | grep "^[a-zA-Z][a-zA-Z]*$")
if [ $numero ]
        then echo $1 es un valor numérico"
elif [ $letra ]
        then echo "$1 es una valor alfabético"
else
        echo "$1 es un valor alfanumérico"
        exit 2
fi
set +x      # cerramos el corte
exit 0

Si ahora ejecutas el script con «bash numero.sh» encontrarás el siguiente resultado,

javier@debian:~/Pruebas$ bash numero.sh 123
El argumento es correcto
++ echo 123
++ grep '^[0-9][0-9]*$'
+ numero=123
++ echo 123
++ grep '^[a-zA-Z][a-zA-Z]*$'
+ letra=
numero.sh: línea 16: EOF inesperado mientras se buscaba un `"' coincidente
numero.sh: línea 21: error sintáctico: no se esperaba el final del fichero
javier@debian:~/Pruebas$ 
Depuración con set
Depuración con set

Se ejecuta el programa y, al llegar al corte y encontrar el error, sale del programa. Y nos indica el error y la linea donde esta.

El interprete imprimirá todas las instrucciones entre «set -x» y «set +x».

También podemos usar «set -v» para hacer un «echo» por la salida estandar con todas las órdenes que se ejecutan.

javier@debian:~/Pruebas$ var=3 ; set -v ; echo $var ; echo $var
var=3 ; set -v ; echo $var ; echo $var
3
3
javier@debian:~/Pruebas$
Depuración con set -v
Depuración con set -v

Es previsible que sepamos por donde buscar el error, en cuyo caso deberemos ejecutar la sentencia justo antes de que comience el código problemático y revertir el efecto cuando ya sepamos que ha pasado.

Y si ejecutas «set -x», te mostrará la orden ejecutada con las sustituciones hechas

javier@debian:~/Pruebas$ var=3 ; set -x ; echo $var ; echo $var
var=3 ; set -x ; echo $var ; echo $var
+ echo 3
3
+ echo 3
3
javier@debian:~/Pruebas$ 
Depuración con set -x
Depuración con set -x

Con estas herramientas de depuración encontrarémos y evitarémos errores en nuestro código, y nos facilitará la vida.

Ejecución paso a paso

Otra opción disponible es emular la ejecución paso a paso disponible para otros lenguajes con el argumento «DEBUG» con «trap». Para ello creamos una librería de funciones con tres de ellas, y las llamamos desde nuestro script. Crea el fichero «libreria-depuracion.sh» con el siguiente contenido:

nano libreria-depuracion.sh

Copia y pega el siguiente código

# debugger.sh: funciones para depuración de código
debug() {
   [ $__untrap -eq 1 ] && return 
   echo "Línea $1: $BASH_COMMAND"
   while true; do
      read -p "> " comm
      [ -z "$comm" ] || [ "$comm" = "n" ] && return
      [ "$comm" = "c" ] && __untrap=1 && return
      eval $comm
   done
}

breakpoint() {
   __untrap=0
   trap 'debug $LINENO' DEBUG
}

continue {
   trap '' DEBUG
}
Librería para depuración
Librería para depuración

Dale permisos de ejecución con «chmod +x libreria-depuracion.sh»

Solo necesitaremos llamar a la libreria y añadir la sentencia «breakpoint» donde queramos crear un punto de ruptura y que empiece la ejecución paso a paso, y «continue» donde queramos que la ejecución prosiga de forma normal.

Y modificamos el contenido de «numero.sh»

#!/bin/bash
. libreria-depuracion.sh

if test $# -ne 1
        then echo "debe haber un argumento"
        exit 1
else
        echo "El argumento es correcto"
fi
source ./libreria-depuracion.sh
breakpoint

numero=$(echo $1 | grep "^[0-9][0-9]*$")
letra=$(echo $1 | grep "^[a-zA-Z][a-zA-Z]*$")
if [ $numero ]
        then echo "$1 es un valor numérico"
elif [ $letra ]
        then echo "$1 es una valor alfabético"
else
        echo "$1 es un valor alfanumérico"
        exit 2
fi
exit 0
Puntos de corte especificados en el código
Puntos de corte especificados en el código

Al ejecutarlo, obtenemos la salida

javier@debian:~/Pruebas$ bash numero.sh 123
bash numero.sh 123
+ bash numero.sh 123
libreria-depuracion.sh: línea 18: continue: solo tiene significado en un bucle `for', `while', o `until'
libreria-depuracion.sh: línea 20: error sintáctico cerca del elemento inesperado `}'
libreria-depuracion.sh: línea 20: `}'
El argumento es correcto
./libreria-depuracion.sh: línea 18: continue: solo tiene significado en un bucle `for', `while', o `until'
./libreria-depuracion.sh: línea 20: error sintáctico cerca del elemento inesperado `}'
./libreria-depuracion.sh: línea 20: `}'
Línea 15: numero=$(echo $1 | grep "^[0-9][0-9]*$")
> 
Línea 16: letra=$(echo $1 | grep "^[a-zA-Z][a-zA-Z]*$")
> 
numero.sh: línea 22: EOF inesperado mientras se buscaba un `"' coincidente
numero.sh: línea 26: error sintáctico: no se esperaba el final del fichero
javier@debian:~/Pruebas$
Ejecución con puntos de corte
Ejecución con puntos de corte

Al pararse la ejecución, podemos:

  • Presionar «Enter» o escribir «n» para ejecutar la línea y avanzar a la siguiente.
  • Escribir «c» para que la ejecución continúe sin más paradas hasta el próximo breakpoint.
  • Evaluar cualquier órden, sin avanzar.

Tienes más información sobre el uso de librerías aquí.

Si tienes algún comentario que hacer sobre este artículo, al pie del post tienes un formulario para hacerlo.

Si quieres contactar conmigo por cualquier otro asunto relacionado con el sitio, en la página de contacto, tienes un formulario más adecuado.

Y para suscribirte y recibir las novedades publicadas, tienes un enlace en pie de la página o desde aquí mismo.

Deja una respuesta

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.