3.6.7. Depuración

sh/bash no es el mejor lenguaje del mundo a la hora de depurar. Es más, su tipado débil es una fuente permanente de errores triviales que a veces cuesta encontrar, como el de equivocarse en la digitalización del nombre de una variable. En cualquier caso, para facilitar la depuración (además de los supuestos orden, claridad y sencillez en la escritura del código), podemos usar las siguientes técnicas:

set -u

Colocar esta sentencia al principio del script provoca que el intérprete genere un error cuando se usa una variable a la que no se ha asignado valor previamente. Por ejemplo:

$ num=1
$ echo $((num+1))
2
$ echo $((nun+1))  # Pero si digitalizamos mal, bash se calla
1
$ set -u           # Ahora sin embargo, no
$ echo $((nun+1))
-bash: nun: variable sin asignar

Nota

Para que el intérprete revierta el efecto, puede escribirse lo mismo, pero con el signo «+» en vez de el «-«:

$ set +u

Tal cosa es aplicable a las demás técnicas que vemos a continuación.

set -e

Provoca que el intérprete cancele la ejecución del programa nada más ejecutar una orden que devuelve error (o sea, un valor distinto de 0).

set -v

Hace un eco en pantalla de todos las ordenes que se ejecutan. Esto puede servirnos en un programa para ver qué órdenes está realmente ejecutando el programa:

$ a=5
$ set -v
$ echo $a
echo $a
5

Es de prever que al depurar un error sepamos la zona por la que se encuentra el error. En ese caso, podríamos usar la sentencia justamente antes de que empiece el código problemático y revertir el efecto cuando ya sepamos que tal código ha pasado.

set -x:

Muestra también la orden ejecutada, pero con las sustituciones ya hechas:

$ a=5
$ set -x
$ echo $a
+ echo 5
5

Nota

Pueden aplicarse -x y -v a la vez lo que propiciará que se nos muestre la orden original y la orden con sustituciones:

$ a=5
$ set -vx
$ echo $a
echo $a
+ echo 5
5
shellcheck

Es un analizador estático de código. Por ejemplo, para el código:

#!/bin/sh

function foobar() {
   echo "Esta función no está declarada según el estándar"
   n=4
   echo $((n + 1.5))
}

foobar

devolvería lo siguiente:

$ shellcheck script.sh
In /tmp/caca.sh line 5:
function foobar() {
^-- SC2112: 'function' keyword is non-standard. Delete it.


In /tmp/caca.sh line 8:
   echo $((n + 1.5))
               ^-- SC2079: (( )) doesn't support decimals. Use bc or awk.
Ejecución paso a paso

Para emular la ejecución paso a paso de los depuradores típicos de otros lenguajes, debemos recurrir a bash aprovechando que permite usar el argumento DEBUG con trap[1]:

# 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
}

Si incorporamos temporalemente estas tres funciones a nuestro código (lo más cómodo es dejarlas en un fichero aparte e cargarlas con source), no necesitamos más que añadir la sentencia breakpoint allá donde queramos crear un punto de ruptura y que empiece la ejecucuon paso a paso y continue a partir de allí donde queramos que la ejecución prosiga de forma normal.

Al pararse la ejecución, podemos:

  • Presionar, simplemente, «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[2].

  • Evaluar cualquier órden, sin avanzar en absoluto.

Ver también

En la fase de depuración (o incluso en la de producción para realizar comprobaciones) puede intersarnos definir la función execute.

Notas al pie