set -x Tracing in Bash im Detail verstehen und gezielt einsetzen
AI generated
$_
#!/
Bash · Tracing · Debugging · CI/CD
set -x Tracing in Bash im Detail
von PS4 bis zur sicheren CI-Pipeline

Die meisten Entwickler kennen set -x nur als schnellen Schalter fuer mehr Ausgabe, nutzen aber nie mehr als die Standardeinstellung. Mit angepasstem PS4, gezielter Umleitung der Trace-Ausgabe und selektivem Ein- und Ausschalten wird set -x vom groben Werkzeug zur praezisen Bash-Tracing-Technik, die auch in CI-Pipelines sicher einsetzbar ist.

17 Min. Lesezeit set -x · PS4 · BASH_XTRACEFD · CI/CD Bash 4.x · 5.x · Linux · macOS

1. Was set -x wirklich zeigt und warum das oft nicht reicht

set -x aktiviert den sogenannten Xtrace-Modus in Bash: Jeder ausgefuehrte Befehl wird vor seiner Ausfuehrung mit allen expandierten Variablen auf Standard-Error ausgegeben. Wer Bash Tracing mit set -x zum ersten Mal einsetzt, ist meist ueberrascht, wie viel Information dabei sichtbar wird: nicht der Quelltext selbst, sondern der tatsaechlich ausgefuehrte Befehl nach Parameter-Expansion, Globbing und Kommando-Substitution. Genau das macht set -x so wertvoll fuer die Fehlersuche, weil es keine Interpretation, sondern die reale Ausfuehrung zeigt.

In der Standardkonfiguration hat Bash Tracing mit set -x allerdings eine erhebliche Schwaeche: Jede Zeile beginnt mit einem simplen +, ohne Zeitstempel, ohne Dateinamen, ohne Zeilennummer. Bei einem laengeren Skript mit hunderten Zeilen Trace-Ausgabe wird es dadurch muehsam, eine bestimmte Zeile im Quellcode einer bestimmten Trace-Zeile zuzuordnen. Genau dieses Problem loest die Anpassung der PS4-Variablen, die im naechsten Abschnitt im Detail behandelt wird.

Ein weiterer Punkt, der bei set -x oft uebersehen wird: Xtrace kann nicht nur global mit set -x im ganzen Skript aktiviert werden, sondern auch nur fuer einzelne Codeabschnitte, indem man es gezielt an- und wieder ausschaltet. Diese Granularitaet ist der Schluessel dazu, set -x von einem groben Debugging-Hammer in ein praezises Werkzeug zu verwandeln, das auch in produktiven Pipelines sinnvoll eingesetzt werden kann, ohne die Ausgabe mit irrelevanten Details zu ueberfluten.


#!/usr/bin/env bash
set -euo pipefail

target_dir="/var/www/html"
files_count=3

# Enable xtrace
set -x
echo "Deploying to $target_dir with $files_count files"
mkdir -p "$target_dir/releases"
# Output on stderr:
# + echo 'Deploying to /var/www/html with 3 files'
# + mkdir -p /var/www/html/releases

# Disable xtrace again
set +x
echo "This line is not traced"

2. PS4 anpassen: Zeitstempel, Zeilennummern und Funktionsnamen

Die Variable PS4 steuert das Praefix jeder Trace-Zeile, die set -x ausgibt. Standardmaessig ist PS4 auf ein einfaches + gesetzt, aber Bash stellt eine Reihe eingebauter Variablen bereit, die sich in PS4 einbetten lassen: $LINENO fuer die aktuelle Zeilennummer, ${BASH_SOURCE[0]} fuer den Dateinamen und ${FUNCNAME[0]:-main} fuer den Namen der aktuell ausgefuehrten Funktion. Damit wird jede Trace-Zeile sofort einer konkreten Stelle im Quellcode zuordenbar, ohne dass man manuell zaehlen muss.

Noch wertvoller wird Bash Tracing, wenn man einen Zeitstempel in PS4 einbaut. Mit $(date '+%s.%N') laesst sich jede Trace-Zeile mit einem Unix-Timestamp samt Nanosekunden versehen, was bei der Analyse von Performance-Problemen unschaetzbar ist: Man sieht direkt, welcher Befehl ungewoehnlich lange gedauert hat, ohne separate Zeitmessung in den Code einbauen zu muessen. Diese Technik wird in produktionsnahen Deployment-Skripten haeufig eingesetzt, um Engpaesse zu identifizieren, die sonst nur durch aufwendiges Profiling sichtbar wuerden.


#!/usr/bin/env bash
set -euo pipefail

# PS4 with timestamp, file, line number and function name
export PS4='+ [$(date "+%H:%M:%S.%N")] ${BASH_SOURCE[0]}:${LINENO} ${FUNCNAME[0]:-main}(): '

deploy_release() {
  local version="$1"
  mkdir -p "/releases/$version"
  cp -r ./build/* "/releases/$version/"
}

set -x
deploy_release "v2.4.1"
set +x

# Sample trace output:
# + [14:32:07.812445123] deploy.sh:9 deploy_release(): mkdir -p /releases/v2.4.1
# + [14:32:07.819881456] deploy.sh:10 deploy_release(): cp -r ./build/* /releases/v2.4.1/

Ein Detail, das leicht zu Fehlern fuehrt: PS4 muss in einfachen Anfuehrungszeichen gesetzt werden, damit die Kommando-Substitution $(date ...) erst bei jeder Trace-Zeile neu ausgewertet wird, nicht einmalig beim Setzen der Variable. Mit doppelten Anfuehrungszeichen wuerde der Zeitstempel nur einmal berechnet und bliebe fuer die gesamte Skriptlaufzeit statisch, was den ganzen Zweck der Zeitmessung zunichtemacht.

3. Tracing gezielt aktivieren und deaktivieren

In produktiven Skripten will man selten das gesamte Skript tracen, sondern nur einen konkret verdaechtigen Abschnitt. Das Bash Tracing-Pattern dafuer: set -x unmittelbar vor dem interessierenden Codeblock, set +x unmittelbar danach. So bleibt die Trace-Ausgabe auf das Wesentliche beschraenkt, und man muss nicht durch hunderte irrelevante Zeilen scrollen, um die eine relevante Stelle zu finden.

Noch flexibler wird dieses Muster mit einer Umgebungsvariable als Schalter: DEBUG=${DEBUG:-0} am Skriptanfang, gefolgt von [[ "$DEBUG" -eq 1 ]] && set -x. So bleibt das Skript im Normalbetrieb still, laesst sich aber bei Bedarf mit DEBUG=1 ./skript.sh ohne Codeaenderung in den Tracing-Modus versetzen. Diese Technik ist besonders praktisch, wenn ein Skript in Produktion laeuft und ein Fehler nur gelegentlich auftritt, sodass staendiges Tracing zu viel irrelevantes Rauschen erzeugen wuerde.


#!/usr/bin/env bash
set -euo pipefail

# Toggle xtrace via environment variable, no code changes needed
DEBUG="${DEBUG:-0}"
if [[ "$DEBUG" -eq 1 ]]; then
  set -x
fi

process_batch() {
  local batch_id="$1"
  echo "Processing batch $batch_id"
}

# Trace only this specific function call, regardless of DEBUG setting
set -x
process_batch "batch-42"
set +x

echo "Batch complete"

# Usage:
#   ./process.sh            — silent, no trace output
#   DEBUG=1 ./process.sh    — full xtrace from the start

Fuer noch feinere Kontrolle kombiniert man beide Techniken: das globale DEBUG-Flag fuer den generellen Modus und lokale set -x/set +x-Bloecke fuer Abschnitte, die auch im Debug-Modus nicht getraced werden sollen, etwa das Verarbeiten von Zugangsdaten. Mit { set +x; } 2>/dev/null laesst sich sogar das Ausschalten selbst unsichtbar machen, sodass die Trace-Ausgabe nicht einmal die Zeile set +x selbst zeigt.

4. Trace-Ausgabe umleiten mit BASH_XTRACEFD

Standardmaessig schreibt set -x seine Ausgabe auf Datei-Deskriptor 2, also Standard-Error, was bedeutet, dass Trace-Zeilen und tatsaechliche Fehlerausgaben des Skripts vermischt werden. Seit Bash 4.1 loest die Variable BASH_XTRACEFD dieses Problem: Setzt man sie auf einen offenen Datei-Deskriptor, schreibt Bash die komplette Trace-Ausgabe dorthin, getrennt von regulaerem stdout und stderr.

In der Praxis oeffnet man dafuer einen eigenen Deskriptor auf eine Logdatei, weist ihn BASH_XTRACEFD zu und aktiviert danach set -x. Das Ergebnis: Die eigentliche Skriptausgabe bleibt sauber auf stdout und stderr, waehrend die komplette Trace-Historie in einer separaten Datei landet, die man bei Bedarf nach einem Fehlerfall analysieren kann, ohne dass sie den regulaeren Betrieb gestoert hat.


#!/usr/bin/env bash
set -euo pipefail

# Open a dedicated file descriptor for trace output
exec 5>"/var/log/deploy-trace-$(date +%Y%m%d-%H%M%S).log"
BASH_XTRACEFD=5

set -x
deploy_step_one() { echo "Step one"; }
deploy_step_two() { echo "Step two"; }

deploy_step_one
deploy_step_two
set +x

# Close the trace file descriptor when done
exec 5>&-

echo "Deployment finished, trace saved separately from stdout"

Ein haeufiger Fehler: BASH_XTRACEFD muss gesetzt werden, bevor set -x aktiviert wird, sonst landet die erste Trace-Ausgabe noch auf stderr, bevor die Umleitung greift. Ausserdem sollte der Deskriptor nach dem Trace-Block wieder geschlossen werden, um Dateihandles nicht unnoetig offen zu halten, besonders in langlaufenden Skripten mit vielen solcher Bloecke.

5. Verschachtelte Funktionsaufrufe im Trace nachvollziehen

Bei tief verschachtelten Funktionsaufrufen wird die Standard-Trace-Ausgabe schnell unuebersichtlich, weil alle Zeilen auf derselben Einrueckungsebene erscheinen, unabhaengig von der tatsaechlichen Verschachtelungstiefe. Bash bietet dafuer die Variable PS4 mit einem eingebauten Mechanismus: Wiederholt man ein Zeichen wie + in PS4, multipliziert Bash es automatisch mit der aktuellen Verschachtelungstiefe, was eine visuelle Einrueckung im Trace erzeugt.

Damit wird auf einen Blick sichtbar, wie tief man sich gerade im Aufrufstapel befindet, ohne den vollen backtrace eines Debuggers zu benoetigen. Kombiniert mit ${FUNCNAME[0]} in PS4 zeigt jede Zeile zusaetzlich, in welcher Funktion sie ausgefuehrt wird, sodass sich der komplette Kontrollfluss auch bei mehrfach verschachtelten Aufrufen direkt aus der Trace-Ausgabe rekonstruieren laesst.


#!/usr/bin/env bash
set -euo pipefail

# The repeated '+' automatically indents by call depth
export PS4='+${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

level_three() { echo "deepest level"; }
level_two()   { level_three; echo "middle level"; }
level_one()   { level_two; echo "top level"; }

set -x
level_one
set +x

# Sample output shows nesting through repeated '+':
# ++++level_three(): echo deepest level
# +++level_two(): echo middle level
# ++level_one(): echo top level

Diese Technik ist besonders wertvoll bei rekursiven Funktionen, bei denen die Verschachtelungstiefe zur Laufzeit variiert und sich nicht statisch aus dem Quellcode ablesen laesst. Ohne die eingebaute Tiefenmultiplikation von PS4 waere es kaum moeglich, aus der reinen Trace-Ausgabe zu erkennen, auf welcher Rekursionsebene ein bestimmter Befehl tatsaechlich ausgefuehrt wurde.

6. set -x kombiniert mit set -euo pipefail

Set -x entfaltet seinen groessten Nutzen in Kombination mit set -euo pipefail, der Standardabsicherung robuster Bash-Skripte. Schlaegt ein Skript mit set -e ab, zeigt der letzte sichtbare Trace-Eintrag vor dem Abbruch exakt den Befehl, der den Fehler ausgeloest hat, samt aller zu diesem Zeitpunkt expandierten Variablenwerte. Das verkuerzt die Fehlersuche massiv, da man nicht raten muss, welche Zeile fuer den Exit-Code verantwortlich war.

Ein zusaetzlicher Trick fuer diese Kombination: Man registriert einen trap auf ERR, der bei Fehlerabbruch automatisch $LINENO und $BASH_COMMAND ausgibt. BASH_COMMAND enthaelt den zuletzt ausgefuehrten Befehl, unabhaengig davon, ob Xtrace aktiv ist. Kombiniert mit set -x liefert das eine doppelte Absicherung: der Trap zeigt gezielt die Fehlerzeile, der laufende Trace zeigt den kompletten Kontext davor.


#!/usr/bin/env bash
set -euo pipefail

# Trap ERR to show exactly which command failed, even without full tracing
trap 'echo "[ERROR] Line $LINENO: command \"$BASH_COMMAND\" failed" >&2' ERR

export PS4='+ ${BASH_SOURCE[0]}:${LINENO}: '
set -x

backup_database() {
  local db_name="$1"
  mysqldump "$db_name" > "/backups/${db_name}.sql"
}

backup_database "shop_production"
set +x

In der Praxis reicht oft schon der ERR-Trap allein aus, um die Fehlerzeile zu identifizieren, waehrend das vollstaendige set -x Tracing nur bei Bedarf zusaetzlich aktiviert wird, um den Zustand vor dem Fehler nachzuvollziehen. Diese zweistufige Strategie vermeidet die Informationsflut eines durchgehenden Traces, waehrend sie im Fehlerfall trotzdem die volle Praezision liefert.

7. Performance-Auswirkungen von set -x

Set -x ist nicht kostenlos. Jede getracete Zeile erfordert die Auswertung von PS4, was insbesondere bei komplexen PS4-Definitionen mit Kommando-Substitution, wie dem Zeitstempel-Beispiel von oben, spuerbaren Overhead verursacht. Bei einer Schleife mit tausenden Iterationen kann durchgehendes set -x Tracing die Laufzeit eines Skripts messbar verlangsamen, teilweise um ein Vielfaches gegenueber der ungetracten Ausfuehrung.

Deshalb gilt als Faustregel: set -x niemals dauerhaft in produktiven Skripten mit hoher Iterationszahl aktivieren, sondern gezielt fuer kurze, verdaechtige Abschnitte einschalten. Fuer laengere Traces in der Fehlersuche empfiehlt es sich, PS4 auf eine minimale Form zu reduzieren, etwa nur Zeilennummer ohne Zeitstempel, solange keine Performance-Analyse im Vordergrund steht. Die Kombination aus selektivem Tracing und minimalem PS4 haelt den Overhead in den meisten Faellen vernachlaessigbar.

8. Tracing in CI-Pipelines ohne Secrets zu leaken

In CI-Pipelines ist set -x ein zweischneidiges Schwert: Es hilft enorm, fehlschlagende Build-Schritte nachzuvollziehen, birgt aber das Risiko, dass Umgebungsvariablen mit Zugangsdaten, API-Keys oder Tokens im Klartext im Build-Log erscheinen, sobald sie in einem getraceten Befehl expandiert werden. Ein Trace-Log, das versehentlich ein Datenbank-Passwort oder einen Deploy-Key enthaelt, ist ein ernstes Sicherheitsrisiko, besonders wenn CI-Logs von mehreren Teammitgliedern einsehbar sind oder archiviert werden.

Die sichere Praxis: Sensible Befehle, die Secrets als Argumente entgegennehmen, grundsaetzlich mit set +x ausfuehren und direkt danach wieder mit set -x fortsetzen, sofern das restliche Skript getraced werden soll. Viele CI-Systeme wie GitLab CI und GitHub Actions maskieren zwar bekannte Secret-Variablen automatisch in der Log-Ausgabe, aber diese Maskierung greift nicht zuverlaessig, wenn ein Secret Teil eines zusammengesetzten Strings ist, etwa in einer URL mit eingebettetem Token. Manuelles Deaktivieren von set -x um solche Stellen bleibt daher die zuverlaessigste Absicherung.


#!/usr/bin/env bash
set -euo pipefail

export PS4='+ ${BASH_SOURCE[0]}:${LINENO}: '
set -x

echo "Starting deployment pipeline"
mkdir -p /tmp/build

# Disable tracing before handling secrets
set +x
curl -H "Authorization: Bearer ${DEPLOY_TOKEN}" \
     -X POST "https://api.mironsoft.de/deploy" > /tmp/build/response.json
set -x

echo "Deployment request sent"
set +x

Ein zusaetzlicher Schutzmechanismus ist es, kritische Secrets erst innerhalb einer Funktion aus einem separaten, ungetraceten Bereich zu lesen und diese Funktion niemals unter set -x aufzurufen. So bleibt das Risiko, dass ein Secret versehentlich im Trace-Log auftaucht, auf ein Minimum reduziert, selbst wenn ein Kollege spaeter set -x global fuer das gesamte Skript aktiviert, ohne die urspruengliche Struktur im Detail zu kennen.

9. set -x im Vergleich zu anderen Tracing-Techniken

Set -x ist die eingebaute, sofort verfuegbare Tracing-Loesung in Bash, aber nicht die einzige Moeglichkeit, die Ausfuehrung eines Skripts nachzuvollziehen. Je nach Anwendungsfall liefern andere Techniken praezisere oder performantere Ergebnisse.

Technik Setup-Aufwand Performance-Overhead Am besten geeignet fuer
set -x (Standard) Keiner Mittel Schnelle Fehlersuche im Alltag
set -x mit angepasstem PS4 Gering Mittel bis hoch Nachvollziehbare, dauerhafte Trace-Logs
BASH_XTRACEFD Gering Mittel Getrennte Trace-Dateien ohne stderr-Vermischung
bashdb Breakpoints Mittel Hoch (interaktiv) Gezielte Analyse einzelner Codepfade
Strukturiertes Logging Hoch (einmalig) Niedrig Dauerhaftes Monitoring in Produktion

In der Praxis bewaehrt sich eine Kombination: set -x mit angepasstem PS4 fuer die spontane Fehlersuche, BASH_XTRACEFD wenn Trace-Ausgabe und regulaere Logs sauber getrennt bleiben sollen, und strukturiertes Logging fuer den dauerhaften Produktionsbetrieb, bei dem set -x zu viel Rauschen erzeugen wuerde. bashdb kommt ins Spiel, sobald set -x Tracing zeigt, in welchem Bereich ein Fehler auftritt, aber die genaue Ursache noch unklar bleibt.

Mironsoft

Shell-Automatisierung, Debugging und CI/CD-Pipelines

CI-Pipeline-Fehler ohne Secrets-Leak nachvollziehen?

Wir richten sicheres, gezieltes Bash-Tracing in euren Deployment- und CI-Skripten ein, mit angepasstem PS4, getrennten Trace-Logs und klaren Regeln, damit keine Zugangsdaten im Build-Log landen.

Tracing-Setup

PS4-Konfiguration und BASH_XTRACEFD fuer nachvollziehbare Logs

Security-Review

Pruefung bestehender CI-Skripte auf Secret-Leaks im Trace-Log

CI-Integration

Selektives Tracing in GitLab CI und GitHub Actions einrichten

10. Zusammenfassung

set -x ist weit mehr als ein einfacher Debug-Schalter, sobald man ueber die Standardeinstellung hinausgeht. Ein angepasstes PS4 mit Zeilennummer, Dateiname und Zeitstempel macht jede Trace-Zeile sofort einer konkreten Stelle im Code zuordenbar. Selektives Ein- und Ausschalten haelt die Ausgabe auf das Wesentliche beschraenkt, statt hunderte irrelevante Zeilen zu produzieren. BASH_XTRACEFD trennt Trace-Ausgabe sauber von regulaerem stdout und stderr, und die repetitive Struktur von PS4 macht selbst tief verschachtelte Funktionsaufrufe auf einen Blick nachvollziehbar.

In CI-Pipelines verlangt der Einsatz von set -x besondere Vorsicht, da unbedacht getracete Secrets in Build-Logs ein ernstes Sicherheitsrisiko darstellen. Die Kombination aus gezieltem An- und Ausschalten rund um sensible Befehle und einem ERR-Trap fuer die schnelle Fehlerlokalisierung liefert die richtige Balance zwischen Nachvollziehbarkeit und Sicherheit. Wer set -x auf diese Weise einsetzt, hat ein praezises, kostenloses Bash-Tracing-Werkzeug, das in den meisten Faellen bereits ausreicht, bevor ein vollwertiger Debugger wie bashdb ueberhaupt noetig wird.

set -x Tracing in Bash — Das Wichtigste auf einen Blick

PS4 anpassen

Zeilennummer, Dateiname und Zeitstempel in PS4 einbetten, in einfachen Anfuehrungszeichen, damit sie pro Zeile neu ausgewertet werden.

Selektives Tracing

set -x/set +x gezielt um verdaechtige Abschnitte, statt das ganze Skript zu tracen.

BASH_XTRACEFD

Trace-Ausgabe in eine eigene Datei umleiten, getrennt von stdout und stderr, seit Bash 4.1.

Secrets schuetzen

Vor sensiblen Befehlen immer set +x, sonst landen Tokens und Passwoerter im CI-Log.

11. FAQ: set -x Tracing in Bash

1Was macht set -x genau?
Aktiviert Xtrace: jeder Befehl wird vor Ausfuehrung mit expandierten Werten auf stderr ausgegeben.
2Wozu dient PS4?
Steuert das Praefix jeder Trace-Zeile, z.B. mit LINENO, BASH_SOURCE und FUNCNAME fuer Zuordenbarkeit.
3Einfache statt doppelte Anfuehrungszeichen bei PS4?
Einfache Anfuehrungszeichen sorgen dafuer, dass Kommando-Substitution pro Zeile neu ausgewertet wird, etwa fuer Zeitstempel.
4Trace in Datei umleiten?
Mit BASH_XTRACEFD seit Bash 4.1, getrennt von stdout und stderr.
5set -x nur fuer Teil des Skripts?
set -x vor, set +x nach dem interessierenden Block. Optional per DEBUG-Umgebungsvariable steuern.
6Verschachtelungstiefe sichtbar machen?
Wiederholtes Zeichen in PS4 wird automatisch mit der Verschachtelungstiefe multipliziert.
7Verlangsamt set -x?
Ja, besonders bei komplexem PS4 und vielen Iterationen. Gezielt statt dauerhaft aktivieren.
8Secrets im CI-Log vermeiden?
set +x vor sensiblen Befehlen setzen. Automatische Maskierung ist nicht zuverlaessig genug.
9Kombination mit set -euo pipefail?
Zeigt beim Abbruch den letzten getraceten Befehl. ERR-Trap mit BASH_COMMAND liefert zusaetzliche Praezision.
10Wann lieber bashdb statt set -x?
Sobald die Fehlerursache trotz Trace unklar bleibt, bietet bashdb gezielte Breakpoints und Variableninspektion.