Depuración en Bash: técnicas prácticas con set -x, set -v y bashdb
Introducción
Cuando escribís scripts en Bash, es común que aparezcan errores sutiles: variables sin inicializar, comandos que fallan silenciosamente o lógica que no se ejecuta como esperabas. Depurar estos problemas puede resultar frustrante si solo recurrís a echo esporádicos. Afortunadamente, Bash ofrece herramientas integradas que permiten ver exactamente qué está ocurriendo en cada paso del script, además de depuradores externos como bashdb que brindan una experiencia más cercana a la de un IDE.
En este tutorial vas a aprender:
- Cómo activar el rastreo de ejecución con
set -xy qué información muestra. - Cómo usar
set -vpara ver cada línea tal como es leída por el intérprete. - Cómo combinar ambas opciones para obtener un diagnóstico más completo.
- Cómo crear un depurador simple usando la señal
DEBUGy untrap. - Una introducción rápida a bashdb, su instalación básica y comandos esenciales.
- Ejemplos concretos de salida de depuración al ejecutar un script que contiene errores intencionales.
- Buenas prácticas y errores frecuentes que evitás al depurar en Bash.
El contenido está pensado para desarrolladores, administradores de sistemas y cualquier persona que escriba o mantenga scripts de shell y quiera ganar confianza al diagnosticar problemas sin depender únicamente de adivinanzas.
Habilitando el rastreo con set -x
La opción -x hace que Bash muestre cada comando antes de ejecutarlo, expandiendo variables y sustituiciones. Cada línea de salida comienza con el carácter + (o con el valor de la variable PS4 si la cambiás). Esto es invaluable para ver exactamente qué se está ejecutando, especialmente cuando las variables cambian de valor inesperadamente.
Ejemplo básico
Creemos un pequeño script que asigna una variable, la imita y luego ejecuta un comando que falla:
#!/usr/bin/env bash
set -x
echo "Inicio del script"
mi_var=42
echo "El valor de mi_var es: $mi_var"
false # este comando devuelve un código de error
echo "Esta línea no se alcanzará"Al ejecutarlo, la salida será algo como:
+ echo 'Inicio del script'
Inicio del script
+ mi_var=42
+ echo 'El valor de mi_var es: 42'
El valor de mi_var es: 42
+ false
+ echo 'Esta línea no se alcanzará'Observá que, pese a que false falla, el script continúa porque no hemos activado set -e. Si quisieramos que el script se detuviera ante el primer error, agregaríamos set -e antes o después de set -x.
Personalizando el prefijo de rastreo
La variable PS4 controla el prefijo que se muestra antes de cada línea de rastreo. Por defecto es + , pero podés cambiarla a cualquier cadena, por ejemplo incluir el número de línea o el nombre del script:
#!/usr/bin/env bash
PS4='+\[$BASH_SOURCE:$LINENO\] '
set -x
echo "Personalizando PS4"Esto produciría algo como:
+[script.sh:5] echo 'Personalizando PS4'
Personalizando PS4Esto es útil cuando tenés scripts largos y necesitás ubicar rápidamente el origen de cada comando.
Mostrando las entradas con set -v
Mientras que set -x muestra los comandos después de que Bash los ha expandido, set -v (verbose) muestra cada línea del script tal como es leída, antes de cualquier sustitución. Esto permite ver el código fuente tal cual estuvo escrito, lo que ayuda a detectar errores de sintaxis o de comillas que pueden quedar ocultos tras la expansión.
Ejemplo de set -v
#!/usr/bin/env bash
set -v
# Esta es una comentario
mi_var="Hola, mundo"
echo $mi_var
# Intencionalmente dejamos una variable sin cerrar para ver el efecto
mal_var="Esto está sin cerrar
echo $mal_varAl correrlo, la salida incluirá las líneas tal como aparecen en el archivo:
#!/usr/bin/env bash
set -v
# Esta es una comentario
mi_var="Hola, mundo"
echo $mi_var
# Intencionalmente dejamos una variable sin cerrar para ver el efecto
mal_var="Esto está sin cerrarLuego Bash intentará ejecutar el comando echo $mi_var y mostrará su salida, seguido del error de sintaxis producido por la comilla faltante. De esta manera podés ver exactamente dónde se rompe la lectura del script.
Combinando set -x y set -v
Usar ambas opciones simultáneamente brinda una vista doble: primero ves la línea tal como está escrita (-v) y luego, después de la expansión, el comando que realmente se ejecuta (-x).
#!/usr/bin/env bash
set -xv
saludo="¡Hola, $USER!"
echo $saludoSalida esperada:
+ set -xv
+ saludo="¡Hola, tuusuario!"
+ echo '¡Hola, tuusuario!'
¡Hola, tuusuario!En la primera línea vemos la asignación tal como fue leída (por -v), luego la expansión de $USER (por -x).
Un depurador simple con trap DEBUG
Bash permite ejecutar una acción cada vez que está a punto de ejecutarse un comando mediante la señal DEBUG. Al combinarla con trap, podemos imprimir información útil como el número de línea, el comando que se va a ejecutar y el valor de ciertas variables.
Implementando un depurador básico
#!/usr/bin/env bash
# Función que se ejecutará antes de cada comando
debug_step() {
echo "[DEBUG] Línea: $LINENO | Comando: '$BASH_COMMAND'"
}
# Asociamos la función a la señal DEBUG
trap 'debug_step' DEBUG
# Ahora el script que queremos observar
print_var() {
local texto="Valor interno"
echo $texto
}
print_var
unset no_existe # esto generará un mensaje de error pero continuamos
echo "Fin"La salida será algo como:
[DEBUG] Línea: 6 | Comando: 'print_var'
[DEBUG] Línea: 9 | Comando: 'local texto="Valor interno"'
[DEBUG] Línea: 10 | Comando: 'echo $texto'
Valor interno
[DEBUG] Línea: 12 | Comando: 'unset no_existe'
[DEBUG] Línea: 13 | Comando: 'echo "Fin"'
FinEste enfoque es ligero y no requiere instalar nada extra. Podés ampliar la función debug_step para mostrar el contenido de variables específicas, el valor de $? o incluso escribir en un archivo de log.
Limitaciones
- El trap
DEBUGse dispara también para comandos internos de Bash comofor,ifofunction. Si necesitás filtrarlos, podés comprobar$BASH_COMMANDdentro de la función. - No se muestra el estado de expansión de variables antes de la ejecución; para eso seguís necesitando
set -x.
Introducción a bashdb
bashdb es un depurador externo escrito en Bash que ofrece puntos de interrupción, paso a paso, inspección de variables y pila de llamadas, semejante a lo que ofrecen gdb o pdb en otros lenguajes. No viene instalado por defecto en la mayoría de distribuciones, pero su instalación es sencilla.
Instalación rápida
En sistemas basados en Debian/Ubuntu:
sudo apt-get update
sudo apt-get install bashdbEn sistemas con dnf (Fedora, RHEL):
sudo dnf install bashdbEn macOS con Homebrew:
brew install bashdbUna vez instalado, podés invocar el depurador sobre cualquier script de Bash:
bashdb mi_script.shEsto lanzará una interfaz similar a la de gdb donde podés usar comandos como:
breakobpara establecer un punto de interrupción en una línea o función.continueocpara ejecutar hasta el siguiente punto de interrupción.stepospara ejecutar la siguiente instrucción y detenerse.printoppara mostrar el valor de una variable.backtraceobtpara ver la pila de llamadas.quitoqpara salir.
Sesión de ejemplo con bashdb
Supongamos el siguiente script con un error lógico:
#!/usr/bin/env bash
suma() {
local a=$1
local b=$2
echo $(( a + b ))
}
resultado=$(suma 5 7)
echo "El resultado es: $resultado"
# Intentamos usar la función con un parámetro faltante
resultado2=$(suma 3)
echo "Resultado con un solo parámetro: $resultado2"Al ejecutarlo normalmente, obtendríamos:
El resultado es: 12
bash: line 6: 3 + : sintaxis error: operando esperado (error token is "+" )
Resultado con un solo parámetro: El segundo llamado a suma falla porque la variable b queda vacía, provocando una expresión aritmética inválida. Con bashdb podríamos detenernos justo antes de la llamada problemática e inspeccionar los valores de a y b.
Una sesión típica sería:
$ bashdb script.sh
(gdb) break 12 # establece un punto en la línea donde llamamos a suma con un parámetro
(gdb) run
... ejecución hasta el punto de interrupción ...
(gdb) print a
$1 = 3
(gdb) print b
$2 = ""
(gdb) step
... ejecuta la línea y muestra el error ...
(gdb) quitEsto muestra cómo bashdb permite aislar el estado exacto de las variables en el momento del fallo, algo que resulta mucho más difícil de lograr con solo echo o set -x.
Ejemplos de salida de depuración al ejecutar un script con errores
A continuación, vamos a crear un script que combine varios fallos típicos: variable sin inicializar, uso incorrecto de comillas, y un comando que devuelve error de salida. Luego lo ejecutaremos con distintas opciones de depuración para observar la salida.
Script de ejemplo
Guarda el siguiente contenido en depurar_errores.sh:
#!/usr/bin/env bash
# Activamos tanto rastreo como verbose para ver todo
set -xv
echo "Inicio de la depuración"
# Variable que no hemos inicializado todavía
echo "Valor de var_sin_init: $var_sin_init"
# Asignación con comillas simples que impiden expansión
mensaje='El valor de USER es: $USER'
echo "$mensaje"
# Intentamos dividir por cero usando let (generará error)
let resultado=10/0
echo "Resultado: $resultado"
# Llamamos a un comando que no existe
comando_inexistente
echo "Fin del script"Ejecutando solo con set -x
#!/usr/bin/env bash
set -x
source depurar_errores.shSalida (fragmento representativo):
+ source depurar_errores.sh
++ echo 'Inicio de la depuración'
Inicio de la depuración
++ echo 'Valor de var_sin_init: '
Valor de var_sin_init:
++ mensaje='El valor de USER es: $USER'
++ echo 'El valor de USER es: $USER'
El valor de USER es: $USER
++ let resultado=10/0
depurar_errores.sh: línea 9: let: división por cero (error token is "0" )
++ echo 'Resultado: '
Resultado:
++ comando_inexistente
depurar_errores.sh: línea 13: comando_inexistente: no se encontró la orden
++ echo 'Fin del script'
Fin del scriptObservá cómo cada línea comienza con + y vemos la expansión de variables y el fallo de let y del comando inexistente.
Ejecutando solo con set -v
#!/usr/bin/env bash
set -v
source depurar_errores.shSalida (fragmento):
#!/usr/bin/env bash
# Activamos tanto rastreo como verbose para ver todo
set -xv
echo "Inicio de la depuración"
# Variable que no hemos inicializado todavía
echo "Valor de var_sin_init: $var_sin_init"
# Asignación con comillas simples que impiden expansión
mensaje='El valor de USER es: $USER'
echo "$mensaje"
# Intentamos dividir por cero usando let (generará error)
let resultado=10/0
echo "Resultado: $resultado"
# Llamamos a un comando que no existe
comando_inexistente
echo "Fin del script"Luego Bash continúa ejecutando y muestra los mensajes de error generados por los comandos fallidos, pero no muestra la expansión de comandos porque no tuvimos -x.
Ejecutando con ambas opciones (set -xv dentro del script)
Como el propio script ya tiene set -xv, basta con ejecutarlo directamente:
./depurar_errores.shLa salida combina lo visto anteriormente: primero la lectura de cada línea (por -v) y luego la ejecución con expansión (por -x). Esto permite correlacionar exactamente qué parte del código fuente produjo cada mensaje de error.
Usando el depurador basado en trap DEBUG
Creemos una versión del script que incluya nuestro depurador simple:
#!/usr/bin/env bash
# Depurador simple
debug_step() {
echo "[DBG] L$LINENO: $BASH_COMMAND"
}
trap 'debug_step' DEBUG
set -e # detenemos ante el primer error para que la salida sea más legible
echo "Inicio"
var_sin_init="" # ahora sí la inicializamos para evitar expansión vacía
echo "var_sin_init=$var_sin_init"
mensaje='El valor de USER es: $USER'
echo "$mensaje"
let resultado=10/0 # este comando provocará la detención por set -e
"Nunca se alcanzará"Al ejecutarlo, la salida será algo como:
[DBG] L6: echo 'Inicio'
Inicio
[DBG] L9: var_sin_init=""
[DBG] L10: echo 'var_sin_init='
var_sin_init=
[DBG] L12: mensaje='El valor de USER es: $USER'
[DBG] L13: echo 'El valor de USER es: $USER'
El valor de USER es: $USER
[DBG] L15: let resultado=10/0
depurar_errores_trap.sh: línea 15: let: división por cero (error token is "0" )Observá que, gracias a set -e, el script se detiene justo después de la línea que falla, y el trap nos mostró cada comando antes de su ejecución, incluida la línea fallida.
Sesión corta con bashdb
Si tuvieras bashdb instalado, podrías lanzar:
bashdb depurar_errores.shY dentro del intérprete de bashdb ejecutar comandos como:
(gdb) break 15 # detención en la línea de let
(gdb) run
(gdb) print resultado # mostrará que la variable aún no tiene valor no está asignada
(gdb) continue # finalizará mostrando el error de división por cero
(gdb) quitEsto ilustra cómo, con un depurador completo, podés inspeccionar el estado del programa en el momento exacto del fallo, algo que resulta muy útil cuando el error depende de valores calculados previamente.
Buenas prácticas y errores comunes al depurar en Bash
Buenas prácticas
Activar
set -euo pipefailen entornos de desarrollo-ehace que el script abandone la ejecución ante el primer comando que devuelva un código distinto de cero.-utrata el uso de variables sin inicializar como error.-o pipefailhace que el valor de retorno de una canalización sea el del último comando que falló, no solo del último.
Estas opciones facilitan la detección temprana de problemas.
Usar
PS4para incluir contexto
PersonalizarPS4con el nombre del script y el número de línea (como vimos) reduce el tiempo de búsqueda en scripts extensos.Separar la lógica en funciones
Depurar funciones individuales es más sencillo porque el alcance de las variables está limitado y los traces son más legibles.Registrar la salida de depuración en un archivo
Si la consola se vuelve muy ruidosa, redirigí la salida deset -xa un archivo:exec 3>&2 2>depuracion.log set -x # ... código ... exec 2>&3Así conservás la interacción normal con la terminal mientras guardás el trace para análisis posterior.
Aprovechar
declare -ppara inspeccionar variables
Dentro de un trap o en puntos estratégicos, usádeclare -p nombre_varpara obtener una representación que pueda ser reutilizada como asignación.Preferir
bashdbpara sesiones interactivas
Cuando necesitás establecer puntos de interrupción, observar la pila o ejecutar paso a paso, bashdb supera ampliamente a losechoyset -x.
Errores comunes
Olvidar comillar las expansiones de variables
Esto provoca división de palabras y expansión de nombres de ruta no deseada, generando errores difíciles de rastrear. Siempre usá"$var"salvo que tengas un motivo específico para no hacerlo.Confundir
set -xconset -v
Recordá que-xmuestra lo que se ejecuta tras la expansión;-vmuestra lo que se lee. Usar solo uno puede dejarte sin información crítica.Depurar en producción sin controlar la verbosidad
Unset -xdejado activado puede llenar los logs de información sensible o degradar el rendimiento. Usá variables de entorno o argumentos para activar el modo depuración solo cuando sea necesario.No resetear
PS4después de modificarlo
Si cambiásPS4dentro de una función o subshell y no lo restaurá, afectará a los traces posteriores de forma inesperada.Depender únicamente de
echopara seguir el flujo
Losechopueden ser eliminados o comentados por error, y no muestran el contexto de expansión. Combinarlos conset -xo un trap es mucho más robusto.
Conclusión
Depurar scripts de Bash no tiene por qué ser una tarea de adivinanzas. Con las herramientas nativas como set -x, set -v y la señal DEBUG, podés obtener una visión detallada de cada paso que da el intérprete, desde la lectura de la línea hasta su ejecución y expansión. Cuando necesitás un control más profundo — puntos de interrupción, inspección de variables y seguimiento de la pila — , bashdb brinda una experiencia de depuración cercana a la de los depuradores tradicionales de otros lenguajes.
En este vimos:
- Cómo activar y personalizar el rastreo con
set -xy cómo ver el código tal como está conset -v. - Cómo construir un depurador ligero usando
trap DEBUGpara mostrar línea y comando antes de cada ejecución. - Una introducción práctica a bashdb, su instalación y los comandos más útiles para controlar la ejecución paso a paso.
- Ejemplos concretos de salida de depuración al ejecutar un script que contiene errores intencionales, mostrando qué información brinda cada técnica.
- Buenas prácticas para hacer que la depuración sea segura, efectiva y poco invasiva, así como los errores más frecuentes que debés evitar.
Aplicando estas técnicas en tus propios scripts, reducirás significativamente el tiempo dedicado a buscar fallos y aumentarás la confianza en la corrección de tus automatizaciones. ¡A depurar!
Nota: Si te interesado que implicar sobre scripts, podés explorar herramientas como shellcheck para análisis estático o integrar la depuración en tus pipelines de CI/CD para que los errores se detecten antes de llegar a producción.
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.