Padroneggiare il Debug degli Script Bash: Tecniche Essenziali per Sviluppatori

Esegui il debug degli script Bash con controlli sintattici, xtrace, modalità strict, trap, ShellCheck e logging mirato.

Padroneggiare il Debug degli Script Bash: Tecniche Essenziali per Sviluppatori

Il debug degli script Bash inizia con una domanda: dove lo script ha fatto qualcosa di diverso da quanto ti aspettavi? Un buon debug ti offre visibilità sugli errori di sintassi, sulle variabili espanse, sull'ordine dei comandi e sugli stati di uscita senza trasformare lo script in rumore.

Questa guida illustra le tecniche pratiche di debug Bash che puoi utilizzare su uno script di automazione reale prima che raggiunga cron, CI o la produzione.

Inizia con un Controllo Sintattico

Prima di tracciare il comportamento a runtime, assicurati che Bash possa analizzare il file:

bash -n ./deploy.sh

bash -n legge lo script e segnala errori di sintassi senza eseguire comandi. Rileva fi, done, then, virgolette e parentesi graffe mancanti. Non rileverà errori logici, file mancanti o comandi che falliscono a runtime.

Ad esempio, questo errore di battitura viene rilevato prima che qualsiasi cosa venga eseguita:

if [ -f "$CONFIG" ]; then
    echo "Config trovato"
# manca fi

Esegui un controllo sintattico dopo modifiche importanti e prima di aggiungere ulteriore output di debug.

Traccia l'Esecuzione con set -x

Il debugger integrato più utile è xtrace:

set -x
some_command "$VALUE"
set +x

Con la traccia abilitata, Bash stampa ogni comando dopo le espansioni e prima dell'esecuzione. Questo ti aiuta a vedere se una variabile è vuota, se un glob è stato espanso o se un comando ha ricevuto argomenti diversi da quelli previsti.

Per la traccia dell'intero script, esegui:

bash -x ./deploy.sh

Per tracce più pulite, imposta PS4 in modo che ogni riga includa il numero di riga del sorgente:

export PS4='+ ${BASH_SOURCE}:${LINENO}: '
bash -x ./deploy.sh

Se il tuo script gestisce segreti, non tracciare le sezioni che stampano token, password o URL firmati. Disattiva la traccia prima di quei comandi:

set +x
login_with_secret "$API_TOKEN"
set -x

Aggiungi la Modalità Strict con Cautela

Queste opzioni rilevano errori comuni prima:

set -euo pipefail

set -e esce in caso di molti fallimenti di comandi non gestiti. set -u tratta le variabili non impostate come errori. set -o pipefail fa fallire una pipeline se un qualsiasi comando nella pipeline fallisce, non solo l'ultimo comando.

Sono utili, ma non sostituiscono la gestione esplicita. Comandi come grep possono restituire 1 per un normale risultato "non trovato":

if grep -q "PRONTO" status.txt; then
    echo "pronto"
else
    echo "non pronto"
fi

Questo è più chiaro che nascondere il risultato con grep -q "PRONTO" status.txt || true.

Stampa i Valori Giusti

Un logging mirato è meglio di righe echo sparse. Stampa i valori che influenzano il ramo che stai debugando:

printf 'DEBUG: user=%q env=%q target=%q\n' "$USER_NAME" "$ENVIRONMENT" "$TARGET_HOST" >&2

printf '%q' mostra i valori con escape shell, rendendo più facili da individuare spazi e caratteri speciali. Invia l'output di debug a stderr in modo che l'output normale dello script rimanga utilizzabile nelle pipeline.

Quando un comando fallisce, cattura immediatamente il suo stato:

run_migration
status=$?

if [ "$status" -ne 0 ]; then
    echo "Migrazione fallita con codice di uscita $status" >&2
    exit "$status"
fi

Non eseguire un altro comando prima di salvare $?, perché anche echo lo sostituisce.

Debug di Cicli e Condizionali

I bug nei cicli spesso derivano dalla suddivisione in parole o da input imprevisti. Racchiudi le variabili tra virgolette e leggi le righe in modo sicuro:

while IFS= read -r line; do
    printf 'line=%q\n' "$line" >&2
done < input.txt

Per i condizionali, stampa i valori esatti che vengono confrontati:

printf 'expected=%q actual=%q\n' "$EXPECTED" "$ACTUAL" >&2

if [[ "$ACTUAL" == "$EXPECTED" ]]; then
    echo "corrispondenza"
fi

Se hai bisogno di mettere in pausa all'interno di uno script durante il debug locale, read funziona:

read -r -p "Premi Invio per continuare..."

Rimuovi le pause prima di committare lo script, specialmente se potrebbe essere eseguito senza supervisione.

Usa ShellCheck per l'Analisi Statica

ShellCheck rileva molti problemi che Bash eseguirà felicemente finché non si rompono in un caso limite:

shellcheck ./deploy.sh

Segnala variabili non quotate, codice irraggiungibile, test sospetti, variabili inutilizzate e problemi di portabilità. Tratta gli avvisi come suggerimenti per ispezionare il codice, non come prova automatica che lo script sia sbagliato. A volte potresti disabilitare intenzionalmente un avviso, ma aggiungi un breve commento che spieghi perché.

Usa trap per Vedere la Riga che Fallisce

Per script più lunghi, un trap di errore può dirti dove si è verificato un fallimento:

set -Eeo pipefail

trap 'echo "Errore alla riga $LINENO: $BASH_COMMAND" >&2' ERR

set -E aiuta il trap ERR a propagarsi nelle funzioni e nelle subshell in Bash. Questo è utile nei log CI dove potresti non avere una shell interattiva.

Conclusione

Inizia con bash -n, usa bash -x o set -x mirato per la traccia a runtime e aggiungi logging mirato su stderr attorno al ramo che si comporta in modo errato. Per script importanti, esegui ShellCheck e aggiungi un trap ERR in modo che i fallimenti puntino al comando e alla riga che necessitano attenzione.