bashdb Debugger: Bash-Skripte Schritt fuer Schritt debuggen
AI generated
$_
#!/
Bash · Debugging · bashdb · Testing
bashdb: Bash-Skripte Schritt fuer Schritt debuggen
vom ersten Breakpoint bis zum Call Stack

Wer ein fehlerhaftes Bash-Skript nur mit verstreuten echo-Zeilen untersucht, verliert Zeit und Uebersicht. Der Bash-Debugger bashdb bringt Breakpoints, Watchpoints und Call-Stack-Analyse in die Shell und macht die Fehlersuche in komplexen Skripten so nachvollziehbar wie in jeder anderen Programmiersprache.

18 Min. Lesezeit bashdb · Breakpoints · Watchpoints · Call Stack Bash 4.x · 5.x · Linux · macOS

1. Warum ein Debugger fuer Bash-Skripte sinnvoll ist

Die meisten Bash-Entwickler debuggen mit echo-Zeilen, die vor und nach verdaechtigen Stellen eingefuegt und spaeter wieder entfernt werden. Das funktioniert bei zehn Zeilen Code, wird aber bei einem gewachsenen Deployment- oder Backup-Skript mit mehreren Funktionen, Schleifen und externen Aufrufen schnell unpraktikabel. Genau hier setzt bashdb an, ein vollwertiger Bash-Debugger, der auf der GNU-Debugger-Infrastruktur aufbaut und Konzepte wie Breakpoints, Step-Ausfuehrung und Variablenanzeige direkt in die Shell bringt.

bashdb ersetzt das Rateraten durch kontrollierte Beobachtung. Statt zu vermuten, an welcher Zeile ein Skript falsch abbiegt, haelt man die Ausfuehrung an genau der Stelle an, die interessiert, und schaut sich den tatsaechlichen Zustand aller Variablen an. Fuer Skripte, die in Produktionsumgebungen laufen und bei Fehlern schwer reproduzierbar sind, ist das ein erheblicher Zeitgewinn gegenueber dem klassischen Trial-and-Error mit temporaeren Ausgabezeilen.

Besonders wertvoll wird ein Bash-Debugger wie bashdb, wenn ein Skript an Kollegen uebergeben wird, die den Code nicht selbst geschrieben haben. Statt sich durch hunderte Zeilen Shell-Code zu lesen, kann man das Skript unter bashdb starten, Schritt fuer Schritt durchgehen und in Echtzeit beobachten, wie Variablen sich veraendern und welche Funktionen tatsaechlich aufgerufen werden. Die folgenden Abschnitte zeigen den kompletten Workflow von der Installation bis zur Analyse eines Call Stacks in einem mehrteiligen Skript.

2. Installation und erster Start von bashdb

Die Installation von bashdb erfolgt auf den meisten Linux-Distributionen ueber den Paketmanager. Unter Debian und Ubuntu heisst das Paket schlicht bashdb, unter Arch Linux liegt es im AUR, und auf macOS installiert Homebrew die aktuelle Version. Nach der Installation steht der Befehl bashdb als eigenstaendiges Kommando zur Verfuegung, das ein Bash-Skript als Argument entgegennimmt und in einer interaktiven Debug-Session startet, statt es direkt auszufuehren.

Der erste Start zeigt sofort den zentralen Unterschied zu normaler Skriptausfuehrung: bashdb haelt vor der ersten Zeile an und wartet auf Eingaben. Von dort aus steuert man die Ausfuehrung ueber kurze Kommandos, die stark an gdb erinnern, dem klassischen GNU-Debugger fuer C-Programme. Wer bereits mit gdb oder aehnlichen Debuggern gearbeitet hat, findet sich in bashdb sofort zurecht, da die Kommandostruktur bewusst analog gehalten wurde.


# Install bashdb on Debian/Ubuntu
sudo apt-get install bashdb

# Install on macOS via Homebrew
brew install bashdb

# Start a debugging session for a script
bashdb ./deploy.sh

# Inside the bashdb prompt (bashdb<1>):
#   l          list source lines around current position
#   n          step to next line (does not enter functions)
#   s          step into the next line (enters functions)
#   c          continue execution until next breakpoint
#   q          quit the debugger session

Ein haeufiges Missverstaendnis beim Einstieg: bashdb fuehrt das Skript nicht in einer separaten Sandbox aus, sondern startet eine echte Bash-Instanz mit angehaengtem Debug-Hook. Das bedeutet, alle Seiteneffekte des Skripts, etwa das Schreiben von Dateien oder das Ausfuehren von curl-Aufrufen, finden tatsaechlich statt, sobald man mit c oder n ueber die entsprechende Zeile hinweggeht. Fuer Skripte mit destruktiven Operationen empfiehlt es sich, vor der ersten Debug-Session eine Kopie der Zielumgebung oder einen Dry-Run-Modus zu verwenden.

3. Breakpoints setzen und Skriptausfuehrung steuern

Der Kern jeder Debug-Session mit bashdb ist der Breakpoint. Mit break DATEI:ZEILE oder kurz b ZEILE haelt die Ausfuehrung genau an der angegebenen Stelle an, sobald sie erreicht wird, egal wie oft die Zeile vorher in Schleifen durchlaufen wurde. Das ist der entscheidende Vorteil gegenueber echo-basiertem Debugging: Ein Breakpoint in einer Funktion, die hundertmal in einer Schleife aufgerufen wird, kann zusaetzlich mit einer Bedingung versehen werden, sodass er nur beim funfzigsten Durchlauf ausloest.

Neben Zeilennummern akzeptiert bashdb auch Funktionsnamen als Breakpoint-Ziel, was besonders bei laengeren Skripten mit vielen kleinen Hilfsfunktionen praktisch ist. Mit break funktionsname haelt die Ausfuehrung beim Eintritt in die Funktion an, unabhaengig davon, von welcher Stelle im Skript sie aufgerufen wird. Die Befehle n (next) und s (step) steuern danach die Feingranularitaet: n ueberspringt Funktionsaufrufe als Ganzes, s springt tatsaechlich in sie hinein.


# bashdb ./backup.sh

# Set a breakpoint at a specific line
bashdb<1> break 42

# Set a breakpoint on a function by name
bashdb<2> break create_backup_archive

# Set a conditional breakpoint (only triggers when condition is true)
bashdb<3> break 42 if $retry_count -ge 3

# List all currently active breakpoints
bashdb<4> info breakpoints

# Continue execution until the next breakpoint is hit
bashdb<5> continue

# Remove a breakpoint by its number
bashdb<6> delete 1

Ein Detail, das oft uebersehen wird: Breakpoints in bashdb bleiben ueber mehrere continue-Aufrufe hinweg aktiv, bis sie explizit mit delete entfernt werden. Bei einer Schleife mit vielen Iterationen kann das dazu fuehren, dass man denselben Breakpoint dutzende Male bestaetigen muss. Die bedingte Variante mit if loest genau dieses Problem, indem sie den Breakpoint nur dann aktiv werden laesst, wenn die angegebene Bash-Bedingung wahr ist, etwa ein Zaehler ueber einem bestimmten Schwellenwert.

4. Variablen inspizieren und Programmzustand pruefen

Sobald die Ausfuehrung an einem Breakpoint angehalten hat, ist die naechste Frage fast immer: Welchen Wert hat diese Variable gerade? bashdb beantwortet das mit dem Befehl print, kurz p, gefolgt vom Variablennamen. Anders als bei einem eingefuegten echo muss man dafuer weder das Skript modifizieren noch neu starten, sondern kann direkt im angehaltenen Zustand beliebig viele Variablen abfragen und danach die Ausfuehrung fortsetzen.

Fuer komplexere Faelle unterstuetzt bashdb auch eval, mit dem sich beliebige Bash-Ausdruecke im aktuellen Kontext auswerten lassen, inklusive Array-Zugriffen und Parameter-Expansionen. Das ist besonders hilfreich, um zu pruefen, ob eine Bedingung, die spaeter im Skript verwendet wird, tatsaechlich so evaluiert wie erwartet, bevor der entsprechende Code-Pfad ueberhaupt erreicht wird.


bashdb<1> break 58
bashdb<2> continue

# Print a simple variable
bashdb<3> print $target_dir
/var/www/html/current

# Print an array element and array length
bashdb<4> print ${backup_files[2]}
bashdb<5> print ${#backup_files[@]}

# Evaluate an arbitrary expression in the current scope
bashdb<6> eval [[ $retry_count -ge $MAX_RETRIES ]] && echo "would abort here"

# Show all local variables in the current function
bashdb<7> info locals

# Change a variable value on the fly to test a code path
bashdb<8> set variable DEBUG=1

Der Befehl set variable erlaubt es sogar, den Wert einer Variablen waehrend der laufenden Debug-Session zu aendern, um einen bestimmten Code-Pfad gezielt zu erzwingen, ohne das Skript neu zu starten. Das ist besonders praktisch, um Fehlerbehandlungscode zu testen, der normalerweise nur unter seltenen Bedingungen erreicht wird, etwa ein Retry-Zaehler, der eine bestimmte Schwelle ueberschreitet.

5. Watchpoints und bedingte Breakpoints

Waehrend ein normaler Breakpoint an eine Zeile gebunden ist, bindet ein Watchpoint in bashdb die Unterbrechung an eine Variable. Mit watch variablenname haelt die Ausfuehrung automatisch an, sobald sich der Wert dieser Variablen aendert, unabhaengig davon, an welcher Zeile das passiert. Das ist unschaetzbar wertvoll, wenn eine Variable irgendwo im Skript einen unerwarteten Wert annimmt und man nicht weiss, welche der zwanzig Zeilen, die sie theoretisch schreiben koennten, tatsaechlich dafuer verantwortlich ist.

Kombiniert man Watchpoints mit Bedingungen, entsteht ein sehr praezises Debugging-Werkzeug: watch status_code if status_code != 0 haelt nur an, wenn die Variable tatsaechlich auf einen Fehlerwert gesetzt wird, nicht bei jeder Zuweisung. Das reduziert die Anzahl der Unterbrechungen drastisch und fuehrt direkt zu der Zeile, die den eigentlichen Fehler verursacht, statt durch harmlose Zwischenzustaende zu iterieren.


bashdb<1> break main
bashdb<2> continue

# Break whenever this variable changes value, anywhere in the script
bashdb<3> watch upload_status

# Break only when the variable changes to a non-zero value
bashdb<4> watch upload_status if upload_status != 0

# Continue until the watched variable actually changes
bashdb<5> continue
Watchpoint 1: upload_status changed
Old value: 0
New value: 22
deploy.sh:87  upload_status=$?

# Inspect the call stack at the moment of the change
bashdb<6> backtrace

Ein Watchpoint verursacht in bashdb spuerbaren Overhead, da nach jeder Zeile geprueft werden muss, ob sich der beobachtete Wert veraendert hat. Fuer kurze Debug-Sessions ist das vernachlaessigbar, bei sehr langen Skripten mit vielen Iterationen sollte man Watchpoints gezielt einsetzen und nach dem Finden der verantwortlichen Zeile mit delete wieder entfernen, um die restliche Ausfuehrung nicht unnoetig zu verlangsamen.

6. Der Call Stack: backtrace und Funktionsaufrufe verfolgen

Wenn ein Fehler tief in einer verschachtelten Funktion auftritt, ist die entscheidende Frage oft nicht, was in dieser Funktion falsch laeuft, sondern von wo aus sie mit welchen Argumenten aufgerufen wurde. Der Befehl backtrace, kurz bt, zeigt in bashdb den vollstaendigen Aufrufstapel: jede Funktion, die aktuell aktiv ist, in der Reihenfolge ihres Aufrufs, zusammen mit der Zeilennummer, an der der jeweilige Aufruf erfolgte.

Das ist besonders wertvoll bei rekursiven Funktionen oder bei Bibliotheksfunktionen, die von mehreren Stellen im Skript aufgerufen werden. Ohne bashdb muesste man an jeder moeglichen Aufrufstelle eine eigene Debug-Ausgabe einfuegen, um herauszufinden, welcher Pfad tatsaechlich zu dem beobachteten Fehler gefuehrt hat. Mit backtrace ist diese Information sofort und vollstaendig verfuegbar, inklusive der Moeglichkeit, mit frame N in eine bestimmte Ebene des Stacks zu wechseln und dort lokale Variablen zu inspizieren.


bashdb<1> break validate_input
bashdb<2> continue

# Show the full call stack at the current breakpoint
bashdb<3> backtrace
#0  validate_input (config.sh:15)
#1  load_configuration (config.sh:34)
#2  main (deploy.sh:120)

# Switch to a specific stack frame to inspect its local variables
bashdb<4> frame 1
bashdb<5> info locals

# Return to the innermost frame
bashdb<6> frame 0

# Step out of the current function back to the caller
bashdb<7> finish

Der Befehl finish fuehrt die aktuelle Funktion bis zu ihrem Ende aus und haelt direkt danach in der aufrufenden Funktion an, mit dem Rueckgabewert der abgeschlossenen Funktion als sofort verfuegbare Information. Das erspart mehrere next-Aufruf und ist besonders effizient, wenn man weiss, dass eine Funktion korrekt arbeitet, aber sehen will, was der Aufrufer mit ihrem Ergebnis anstellt.

7. bashdb in Skripten mit mehreren Dateien

Groessere Bash-Projekte bestehen selten aus einer einzigen Datei. Stattdessen wird ein Haupteinstiegspunkt mit source lib/utils.sh um Bibliotheksfunktionen ergaenzt, die in separaten Dateien gepflegt werden. bashdb folgt source-Aufrufen automatisch und erlaubt Breakpoints in jeder eingebundenen Datei, genau wie im Hauptskript selbst. Das ist ein deutlicher Vorteil gegenueber echo-basiertem Debugging, bei dem man in jeder betroffenen Datei manuell Ausgaben einfuegen muesste.

Um einen Breakpoint gezielt in einer bestimmten Datei zu setzen, wird der Dateiname vor die Zeilennummer geschrieben: break lib/utils.sh:23. Das ist wichtig, sobald mehrere eingebundene Dateien Zeilennummern haben, die sich ueberschneiden koennten, oder wenn Funktionsnamen in verschiedenen Bibliotheken zufaellig identisch sind. bashdb zeigt beim Anhalten immer den vollstaendigen Pfad der aktuellen Datei an, sodass jederzeit klar ist, in welchem Teil des Projekts man sich gerade befindet.


# Project structure:
# deploy.sh          — main entry point, sources lib/*.sh
# lib/utils.sh        — helper functions
# lib/notifications.sh — Slack/email notification functions

# Start debugging the main entry point
bashdb<1> bashdb ./deploy.sh

# Set a breakpoint inside a sourced library file
bashdb<2> break lib/utils.sh:23

# Set a breakpoint by function name, regardless of which file defines it
bashdb<3> break send_slack_notification

# Show which file and line the debugger is currently stopped at
bashdb<4> info line

# List all sourced files known to the current session
bashdb<5> info sources

Ein haeufiger Stolperstein bei mehrteiligen Skripten: Wenn eine Bibliotheksdatei per source mit relativem Pfad eingebunden wird, muss bashdb im gleichen Arbeitsverzeichnis gestartet werden wie das eigentliche Skript, sonst schlaegt das Sourcen bereits vor Erreichen des ersten Breakpoints fehl. Es empfiehlt sich, im Hauptskript grundsaetzlich mit $(dirname "${BASH_SOURCE[0]}") zu arbeiten, um Pfade unabhaengig vom aktuellen Arbeitsverzeichnis aufzuloesen, auch unter bashdb.

8. Typische Debugging-Szenarien mit bashdb

Ein klassisches Szenario: Ein Deployment-Skript bricht in der Produktion mit einem unklaren Fehler ab, laesst sich lokal aber nicht reproduzieren. Mit bashdb startet man das Skript in einer Staging-Umgebung, setzt einen Breakpoint kurz vor dem vermuteten Fehlerbereich und geht Zeile fuer Zeile durch, bis der tatsaechliche Zustand sichtbar wird, der zum Absturz fuehrt. Oft zeigt sich dabei, dass eine Umgebungsvariable in Produktion einen anderen Wert hat als lokal angenommen, ein Fehler, der mit echo-Debugging leicht uebersehen wird, weil man nicht weiss, wonach man suchen soll.

Ein zweites typisches Szenario betrifft Race Conditions in Skripten mit Hintergrundprozessen. Da bashdb die Ausfuehrung des Hauptprozesses anhaelt, lassen sich Timing-abhaengige Fehler damit zwar nicht direkt reproduzieren, aber der Zustand vor dem Start der Hintergrundprozesse laesst sich exakt pruefen, was oft ausreicht, um falsch initialisierte Variablen oder fehlende Locks als Ursache auszuschliessen oder zu bestaetigen. In Kombination mit gezielten Watchpoints auf gemeinsam genutzte Variablen laesst sich so auch die Reihenfolge nebenlaeufiger Schreibzugriffe rekonstruieren.

Ein drittes Szenario ist die Analyse von Skripten, die von Dritten uebernommen wurden und keine oder unzureichende Dokumentation haben. Statt den kompletten Code zeilenweise zu lesen, startet man das Skript unter bashdb, setzt einen Breakpoint auf main und arbeitet sich mit next und step durch den tatsaechlichen Kontrollfluss, wobei die Debug-Ausgabe automatisch zeigt, welche Funktionen tatsaechlich aufgerufen werden und welche toter Code sind.

9. bashdb im Vergleich zu anderen Debugging-Methoden

Die Wahl der richtigen Debugging-Methode haengt stark vom Kontext ab: ein einmaliger Bug in einem kurzen Skript rechtfertigt selten den Aufwand einer vollen bashdb-Session, waehrend ein komplexes, mehrteiliges Automatisierungsprojekt ohne strukturiertes Debugging kaum wartbar bleibt.

Methode Aufwand Praezision Am besten geeignet fuer
echo-Debugging Sehr gering Niedrig Sehr kurze, einmalige Skripte
set -x Tracing Gering Mittel Ganze Ausfuehrung protokollieren
bashdb Breakpoints Mittel Sehr hoch Komplexe, mehrteilige Skripte
bashdb Watchpoints Mittel Sehr hoch Unerwartete Variablenaenderungen
Unit-Tests mit bats Hoch (einmalig) Hoch Wiederkehrende Regressionspruefung

In der Praxis schliessen sich diese Methoden nicht aus, sondern ergaenzen sich. Ein grober set -x-Trace zeigt schnell, in welchem Bereich des Skripts der Fehler ueberhaupt auftritt, und erst danach lohnt sich der gezieltere Einsatz von bashdb, um die genaue Ursache mit Breakpoints und Watchpoints einzugrenzen. Unit-Tests wiederum verhindern, dass derselbe Fehler nach der Behebung durch bashdb erneut auftritt, da sie den korrigierten Code-Pfad dauerhaft absichern.

Mironsoft

Shell-Automatisierung, Debugging und Deployment-Infrastruktur

Fehlerhafte Deployment-Skripte zuverlaessig aufspueren?

Wir analysieren bestehende Bash-Skripte mit bashdb und weiteren Debugging-Werkzeugen, finden die tatsaechliche Fehlerursache statt Symptome zu bekaempfen und richten strukturierte Debugging-Workflows fuer euer Team ein.

Fehleranalyse

bashdb-Sessions fuer schwer reproduzierbare Produktionsfehler

Code-Review

Uebernahme unbekannter Skripte mit Debugger-gestuetzter Analyse

Team-Workshop

Debugging-Workflows und Tooling fuer euer Entwicklerteam einfuehren

10. Zusammenfassung

bashdb verwandelt das Debuggen von Bash-Skripten von einem Ratespiel mit temporaeren echo-Zeilen in einen kontrollierten, nachvollziehbaren Prozess. Breakpoints halten die Ausfuehrung an genau der Stelle an, die interessiert. Watchpoints zeigen, wo eine Variable ihren Wert unerwartet aendert, ohne dass man jede moegliche Schreibstelle im Skript manuell pruefen muss. Der Call Stack mit backtrace macht sichtbar, ueber welchen Pfad eine Funktion tatsaechlich erreicht wurde.

Fuer kurze Einzeiler bleibt echo-Debugging weiterhin die pragmatischste Wahl. Sobald ein Skript aber mehrere Dateien, verschachtelte Funktionen oder produktionsnahe Fehler umfasst, die sich lokal nicht ohne Weiteres reproduzieren lassen, liefert bashdb die Praezision, die echo-Ausgaben nicht bieten koennen. In Kombination mit set -x Tracing fuer den groben Ueberblick und Unit-Tests zur dauerhaften Absicherung entsteht ein vollstaendiger Debugging- und Qualitaetssicherungs-Workflow fuer Bash-Projekte jeder Groesse.

bashdb Debugger — Das Wichtigste auf einen Blick

Breakpoints

break ZEILE oder break funktion haelt die Ausfuehrung an genau der gewuenschten Stelle an, optional mit Bedingung ueber if.

Watchpoints

watch variable haelt automatisch an, sobald sich der Wert einer Variablen aendert, unabhaengig von der Zeile.

Call Stack

backtrace zeigt alle aktiven Funktionsaufrufe samt Aufrufreihenfolge und Zeilennummern.

Mehrteilige Skripte

bashdb folgt source-Aufrufen automatisch, Breakpoints funktionieren auch in eingebundenen Bibliotheksdateien.

11. FAQ: bashdb Debugger fuer Bash-Skripte

1Was ist bashdb genau?
Ein Bash-Debugger auf GNU-Debugger-Basis mit Breakpoints, Step-Ausfuehrung, Watchpoints und Call-Stack-Analyse, aehnlich gdb fuer C-Programme.
2Fuehrt bashdb in einer Sandbox aus?
Nein, es ist eine echte Bash-Instanz. Alle Seiteneffekte finden tatsaechlich statt, sobald die Zeile ausgefuehrt wird.
3Breakpoint vs. Watchpoint?
Breakpoint ist an eine Zeile/Funktion gebunden. Watchpoint ist an eine Variable gebunden und haelt bei jeder Wertaenderung an.
4Bedingte Breakpoints moeglich?
Ja, mit break ZEILE if BEDINGUNG. Loest nur aus wenn die Bash-Bedingung wahr ist.
5Mehrteilige Skripte unterstuetzt?
Ja, bashdb folgt source automatisch. Breakpoints via break datei:zeile in eingebundenen Dateien moeglich.
6Aufrufstapel anzeigen?
Mit backtrace (bt). Zeigt alle aktiven Aufrufe samt Zeilennummern, frame N wechselt zwischen Ebenen.
7Variable live aendern?
Ja, set variable NAME=WERT. Nuetzlich um seltene Fehlerpfade gezielt zu erzwingen.
8Watchpoint verlangsamt Skript?
Ja, spuerbar bei langen Skripten. Gezielt setzen und nach Fund wieder entfernen.
9Wann bashdb statt echo?
Sobald mehrere Funktionen, Dateien oder schwer reproduzierbare Produktionsfehler im Spiel sind.
10Ersetzt bashdb Unit-Tests?
Nein. bashdb findet die Ursache, Unit-Tests verhindern die Wiederkehr. Beide ergaenzen sich.