0Pricing
Linux Command Line & Bash Scripting Mastery · Lezione

Gestori trap per pulizia e segnali

Registri trap EXIT, ERR e INT per rimuovere file temporanei e annullare in modo affidabile il lavoro parziale.

Gestori trap per pulizia e segnali è una lezione Linux Command Line & Bash Scripting Mastery gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Linux Command Line & Bash Scripting Mastery, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.

Perché i gestori trap sono importanti

Quando uno script Bash termina — normalmente, a causa di un errore o perché l'utente ha premuto Ctrl+C — può lasciare file temporanei, dati scritti solo parzialmente o uno stato incoerente. Senza una pulizia, questi residui si accumulano e causano problemi difficili da diagnosticare.

I gestori trap risolvono il problema registrando una funzione o un comando che Bash esegue automaticamente quando riceve uno specifico segnale o pseudosegnale.

  • EXIT — viene attivato ogni volta che lo script termina, per qualsiasi motivo
  • ERR — viene attivato dopo che un comando restituisce un codice di uscita diverso da zero
  • INT — viene attivato quando l'utente invia SIGINT (Ctrl+C)
  • TERM — viene attivato alla ricezione di SIGTERM (ad esempio da kill)

Uno script ben scritto registra questi trap all'inizio, prima che cominci qualsiasi operazione rischiosa.

La sintassi del builtin trap

Il builtin trap registra un gestore per uno o più segnali o pseudosegnali. La sintassi generale è:

trap 'command_or_function' SIGNAL [SIGNAL...]

Regole principali:

  • Il primo argomento è una stringa tra virgolette contenente codice shell (oppure il nome di una funzione) da eseguire quando il segnale viene attivato.
  • È possibile elencare più segnali dopo il gestore.
  • trap '' SIGNAL ignora il segnale (gestore vuoto).
  • trap - SIGNAL ripristina il comportamento predefinito del segnale.

I trap vengono ereditati dalle funzioni chiamate nella stessa shell, ma non dalle subshell avviate con ( ) o &.

#!/usr/bin/env bash
# Syntax examples — not a full script

# Register a cleanup function on EXIT
trap cleanup EXIT

# Inline handler for INT and TERM
trap 'echo "Interrupted!"; exit 1' INT TERM

# Ignore SIGHUP
trap '' HUP

# Reset SIGPIPE to default
trap - PIPE

Il trap EXIT — la Sua rete di sicurezza

Lo pseudosegnale EXIT è il trap più importante da registrare. Viene attivato quando la shell termina per qualsiasi motivo: completamento normale, exit N, errore non gestito o segnale. È quindi ideale per rimuovere i file temporanei.

Procedura consigliata:

  • Crei i file temporanei con mktemp, in modo che il nome sia univoco e imprevedibile.
  • Salvi immediatamente il percorso in una variabile.
  • Registri il trap EXIT subito dopo la creazione, affinché la pulizia venga sempre eseguita.

Il codice di uscita dello script viene preservato: il codice di uscita del gestore trap viene ignorato, a meno che non chiami esplicitamente exit al suo interno.

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

TMPFILE=$(mktemp /tmp/myapp.XXXXXX)

cleanup() {
    echo "Removing temp file: $TMPFILE" >&2
    rm -f "$TMPFILE"
}
trap cleanup EXIT

# Do real work
echo "Processing..." > "$TMPFILE"
sort "$TMPFILE" -o "$TMPFILE"
cp "$TMPFILE" /tmp/myapp_result.txt

echo "Done. Result saved."

Gestione di più file e directory temporanei

Gli script reali creano spesso più risorse temporanee: file, directory, FIFO e file di lock. Invece di tenere traccia di ciascuna separatamente, raccolga tutto in un array e rimuova ogni elemento in un'unica funzione di pulizia.

Questo schema si adatta facilmente: ogni volta che crea una nuova risorsa temporanea, la aggiunga all'array. Il trap EXIT gestirà sempre l'elenco completo, indipendentemente dal punto in cui lo script termina.

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

CLEANUP_TARGETS=()

add_cleanup() {
    CLEANUP_TARGETS+=("$1")
}

cleanup() {
    echo "Running cleanup..." >&2
    for target in "${CLEANUP_TARGETS[@]:-}"; do
        rm -rf "$target"
        echo "Removed: $target" >&2
    done
}
trap cleanup EXIT

# Create and track temp resources
TMPDIR_WORK=$(mktemp -d /tmp/work.XXXXXX)
add_cleanup "$TMPDIR_WORK"

TMPFILE_LOG=$(mktemp /tmp/run.XXXXXX.log)
add_cleanup "$TMPFILE_LOG"

echo "Working in $TMPDIR_WORK" | tee "$TMPFILE_LOG"
touch "$TMPDIR_WORK/output.txt"
echo "Step complete."

Il trap ERR — intercettare gli errori

Lo pseudosegnale ERR viene attivato dopo che un comando semplice restituisce un codice di uscita diverso da zero, a condizione che l'opzione della shell set -e (errexit) sia attiva oppure che il comando non si trovi all'interno di un costrutto composto if, while o ||/&&.

Usi il trap ERR per:

  • Registrare la riga che ha causato l'errore usando $LINENO e $BASH_COMMAND.
  • Avviare la logica di rollback prima dell'esecuzione del trap EXIT.
  • Inviare messaggi di errore strutturati allo standard error.

Importante: i trap ERR non vengono attivati all'interno delle funzioni, a meno che non esegua esplicitamente set -E (errtrace), così che le funzioni ereditino il trap ERR dalla shell chiamante.

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

on_error() {
    local exit_code=$?
    local line=$1
    echo "ERROR: command failed with exit code $exit_code at line $line" >&2
    echo "  Command: $BASH_COMMAND" >&2
}
trap 'on_error $LINENO' ERR

echo "Starting task..."
cp /nonexistent/file /tmp/   # This will fail
echo "This line is never reached."

Il trap INT — gestire Ctrl+C correttamente

La pressione di Ctrl+C invia SIGINT al gruppo di processi in primo piano. Senza un trap, Bash termina immediatamente e può lasciare operazioni parziali.

La registrazione di un trap INT consente di:

  • Stampare un messaggio di annullamento chiaro per l'utente.
  • Eseguire un eventuale rollback intermedio prima di passare il controllo al trap EXIT.
  • Terminare con un codice di stato significativo (130 è il codice convenzionale per SIGINT: 128 + 2).

Al termine del gestore INT, chiami exit 130 per propagare il codice di uscita corretto. Il trap EXIT verrà quindi eseguito automaticamente.

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

TMPFILE=$(mktemp /tmp/demo.XXXXXX)

cleanup() {
    rm -f "$TMPFILE"
    echo "Temp file removed." >&2
}

on_interrupt() {
    echo "" >&2
    echo "Caught SIGINT — cancelling gracefully." >&2
    exit 130   # EXIT trap (cleanup) runs automatically after this
}

trap cleanup EXIT
trap on_interrupt INT

echo "Running long task. Press Ctrl+C to cancel."
for i in $(seq 1 10); do
    echo "Step $i/10..."
    sleep 1
done
echo "All steps complete."

Combinare i trap EXIT, ERR e INT

Negli script di produzione, in genere si registrano tutti e tre i trap insieme, per coprire ogni percorso di uscita:

  • EXIT — viene sempre eseguito e gestisce la pulizia dei file.
  • ERR — registra il comando che ha causato l'errore e il numero della riga.
  • INT — stampa un messaggio di annullamento e termina con il codice 130.

Poiché EXIT viene sempre eseguito per ultimo, la logica di pulizia deve trovarsi in un solo punto. I gestori ERR e INT possono concentrarsi sulla registrazione e sull'impostazione dello stato, delegando poi la pulizia effettiva a EXIT chiamando exit.

Usi set -eEuo pipefail all'inizio, affinché anche gli errori nelle funzioni attivino ERR e pipefail rilevi gli errori all'interno delle pipeline.

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

TMPFILE=$(mktemp /tmp/combined.XXXXXX)
ROLLBACK_NEEDED=false

cleanup() {
    if [[ "$ROLLBACK_NEEDED" == true ]]; then
        echo "Rolling back partial changes..." >&2
    fi
    rm -f "$TMPFILE"
    echo "Cleanup done." >&2
}

on_error() {
    ROLLBACK_NEEDED=true
    echo "ERR at line $1: $BASH_COMMAND" >&2
}

on_interrupt() {
    echo "Interrupted by user." >&2
    exit 130
}

trap cleanup       EXIT
trap 'on_error $LINENO' ERR
trap on_interrupt  INT

echo "hello" > "$TMPFILE"
echo "Script finished successfully."

Ripristinare e disabilitare dinamicamente i trap

A volte è necessario modificare un trap durante l'esecuzione dello script: ad esempio, per eseguire una pulizia una tantum al termine di una sezione critica, evitando che le successive operazioni normali attivino un rollback non necessario.

  • trap - SIGNAL ripristina il comportamento predefinito integrato del segnale.
  • trap '' SIGNAL ignora completamente il segnale (il processo non può essere terminato tramite quel segnale).
  • È possibile registrare un nuovo gestore in qualsiasi momento; prevale l'ultima chiamata a trap.

Uno schema comune consiste nell'aggiornare il trap EXIT dopo il completamento corretto di un'operazione rischiosa, sostituendo un gestore di rollback con un semplice gestore di pulizia.

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

TMPFILE=$(mktemp /tmp/staged.XXXXXX)

rollback() {
    echo "ROLLBACK: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

cleanup_only() {
    echo "CLEANUP: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

# Start with rollback in case something fails during the risky phase
trap rollback EXIT

echo "Performing risky operation..."
echo "critical data" > "$TMPFILE"
# ... imagine more risky steps here ...

# Risky phase succeeded — switch to simple cleanup
trap cleanup_only EXIT

echo "Risky phase done. Now doing safe finalization."
cp "$TMPFILE" /tmp/staged_result.txt
echo "All done."

Propagare i codici di uscita attraverso i trap

Esiste un problema insidioso: se la funzione di pulizia contiene comandi che possono fallire, il codice di uscita finale dello script potrebbe essere sovrascritto. Preservi esplicitamente il codice di uscita originale.

All'interno di un gestore trap, $? contiene il codice di uscita che ha attivato il trap. Lo memorizzi nella prima riga del gestore, prima che qualsiasi altro comando lo modifichi.

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

TMPFILE=$(mktemp /tmp/exitcode.XXXXXX)

cleanup() {
    local original_exit=$?   # capture BEFORE any other command
    echo "Cleaning up (original exit: $original_exit)" >&2
    rm -f "$TMPFILE"
    # Exit with the original code so callers see the real result
    exit "$original_exit"
}
trap cleanup EXIT

echo "Doing work..."
echo "data" > "$TMPFILE"

# Simulate failure
if [[ ! -f /tmp/required_marker ]]; then
    echo "Required marker missing!" >&2
    exit 2
fi

echo "Done."

Trap nelle funzioni e nelle subshell

Comprendere l'ereditarietà dei trap è fondamentale per scrivere script corretti:

  • Le funzioni vengono eseguite nella stessa shell ed ereditano i trap, ma ERR viene propagato alle funzioni solo quando è attivo set -E (errtrace).
  • Le subshell (( ... )) iniziano con una copia dei trap del processo padre, ma le modifiche effettuate nella subshell non influiscono sul processo padre.
  • I processi in background (cmd &) sono subshell e non ereditano i trap INT/TERM del processo padre (usano la gestione predefinita dei segnali).

Regola pratica: mantenga la registrazione dei trap nel corpo dello script principale, usi set -E quando è necessario propagare ERR nelle funzioni e non faccia mai affidamento sull'ereditarietà dei trap attraverso &.

#!/usr/bin/env bash
set -eEuo pipefail   # -E = errtrace: ERR propagates into functions

on_error() {
    echo "ERR in function or main at line $1" >&2
}
trap 'on_error $LINENO' ERR

risky_function() {
    echo "Inside risky_function"
    ls /nonexistent_path   # triggers ERR — works because of set -E
}

# Subshell: has its own copy; changes don't affect parent
(
    trap '' ERR   # disable ERR only inside subshell
    ls /nonexistent_path 2>/dev/null || true
    echo "Subshell completed without triggering parent ERR"
)

risky_function

Schema reale: sostituzione atomica di un file

Un classico schema difensivo in Bash è la sostituzione atomica di un file: scrivere in un file temporaneo e poi rinominarlo nella posizione definitiva. Se qualcosa fallisce prima della ridenominazione, l'originale rimane intatto. I trap rendono questo schema estremamente affidabile.

Procedura:

  1. Crei un file temporaneo nello stesso filesystem della destinazione, in modo che mv sia atomico.
  2. Registri un trap EXIT per rimuovere il file temporaneo in caso di uscita anticipata.
  3. Scriva e convalidi il nuovo contenuto.
  4. Esegua la ridenominazione atomica (mv): solo a questo punto l'originale viene sostituito.
  5. Il trap EXIT rimuove il file temporaneo solo se esiste ancora (dopo un mv riuscito, non esiste più).
#!/usr/bin/env bash
set -euo pipefail

DEST="/tmp/important_config.conf"
TMPFILE=$(mktemp "$(dirname "$DEST")/.tmp.XXXXXX")

cleanup() {
    rm -f "$TMPFILE"   # no-op if mv already moved it
}
trap cleanup EXIT

# Write new content to temp file
cat > "$TMPFILE" <<'EOF'
[settings]
version=2
mode=production
EOF

# Validate before replacing
if ! grep -q 'version=' "$TMPFILE"; then
    echo "Validation failed — aborting replacement" >&2
    exit 1
fi

# Atomic rename — DEST is replaced only here
mv "$TMPFILE" "$DEST"
echo "Config updated atomically: $DEST"

Verifica delle conoscenze: ereditarietà del trap ERR

Verifichi la Sua comprensione del comportamento del trap ERR con le funzioni in Bash.

Riepilogo: gestori trap per la pulizia e i segnali

In questa lezione ha imparato a rendere gli script Bash più resilienti attraverso i gestori trap:

  • EXIT è la Sua rete di sicurezza universale: lo registri subito dopo aver creato una risorsa temporanea e acquisisca sempre prima $?, così da preservare il codice di uscita originale.
  • ERR consente di registrare gli errori con $LINENO e $BASH_COMMAND; usi set -E affinché venga ereditato dalle funzioni.
  • INT gestisce Ctrl+C correttamente: stampi un messaggio e termini con il codice 130, lasciando a EXIT il compito della pulizia.
  • Raccolga le risorse temporanee in un array e le rimuova tutte in un'unica funzione di pulizia.
  • Usi trap - SIGNAL per ripristinare i segnali e trap '' SIGNAL per ignorarli dinamicamente.
  • Lo schema della ridenominazione atomica (mktemp → scrittura → convalida → mv), combinato con un trap EXIT, garantisce che le scritture parziali non danneggino mai il file di destinazione.

La combinazione di set -eEuo pipefail con trap posizionati correttamente fornisce una solida base difensiva per qualsiasi script Bash di produzione.

Domande Frequenti

La lezione «Gestori trap per pulizia e segnali» è gratuita?

Sì — il testo completo di «Gestori trap per pulizia e segnali» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Linux Command Line & Bash Scripting Mastery, passa a CoddyKit PRO. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.

Cosa imparerò in «Gestori trap per pulizia e segnali»?

Registri trap EXIT, ERR e INT per rimuovere file temporanei e annullare in modo affidabile il lavoro parziale. Eserciti Linux Command Line & Bash Scripting Mastery con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Linux Command Line & Bash Scripting Mastery?

Non è richiesta alcuna esperienza precedente. Linux Command Line & Bash Scripting Mastery su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Gestori trap per pulizia e segnali»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Linux Command Line & Bash Scripting Mastery?

Sì. Ogni lezione Linux Command Line & Bash Scripting Mastery include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Modalità rigorosa con set -euo pipefail
  2. Gestori trap per pulizia e segnali
  3. File temporanei sicuri e directory di lock
  4. Script idempotenti e logica di retry con backoff
← Torna a Linux Command Line & Bash Scripting Mastery