set -e è una delle opzioni più fraintese di Bash. L’idea di base è semplice: se un comando fallisce, lo script termina invece di andare avanti come se nulla fosse. Nella pratica, però, il comportamento reale dipende dal contesto: una pipeline, un if, un while, una sostituzione di comando o una funzione possono cambiare completamente l’effetto di set -e. Per questo motivo non va trattato come un interruttore magico per “rendere sicuro” uno script.
La regola utile da ricordare è questa: set -e serve a interrompere l’esecuzione quando qualcosa va storto, ma non sostituisce una gestione esplicita degli errori. Se vuoi script affidabili, devi sapere dove Bash decide di ignorare il fallimento e dove invece lo considera fatale.
Cosa fa davvero set -e
Quando abiliti set -e, Bash prova a uscire dallo script non appena un comando restituisce uno stato diverso da zero. In termini pratici, questo evita che uno script continui dopo un errore banale come un file mancante, un comando non trovato o un’operazione fallita su filesystem, database o rete.
Il punto critico è che Bash non applica questa regola in modo assoluto. Esistono contesti in cui un comando può fallire senza far terminare lo script. Questo comportamento è storico, documentato male in molti esempi online, e spesso causa più problemi di quanti ne risolva se usato senza criterio.
Quando set -e è utile
Ci sono casi in cui set -e è sensato e riduce davvero gli errori silenziosi. Per esempio, in script di deploy, backup, provisioning o manutenzione, se fallisce un passaggio intermedio spesso non ha senso proseguire. In questi scenari, l’uscita immediata è preferibile a un’esecuzione parziale che lascia il sistema in uno stato incoerente.
Esempio tipico:
#!/usr/bin/env bash
set -e cd /var/www/app
composer install --no-dev --optimize-autoloader
systemctl reload php-fpmSe cd fallisce, non ha senso eseguire il resto. Se composer install fallisce, meglio fermarsi prima di ricaricare servizi con file incompleti. In questi casi set -e è una rete di sicurezza minimale, non una strategia completa.
Le trappole vere: quando Bash ignora l’errore
Il problema non è che set -e non funzioni; il problema è che funziona in modo non intuitivo. Alcuni contesti annullano l’effetto dell’uscita automatica. Ecco i casi più importanti da conoscere.
1. Condizioni di if, while e until
Se un comando fallisce dentro la condizione di un if o di un ciclo while/until, Bash spesso considera quel fallimento parte della logica condizionale e non un errore fatale.
#!/usr/bin/env bash
set -e if grep -q 'token' /etc/app.conf; then echo 'token trovato'
fi echo 'lo script continua comunque se grep non trova nulla'Qui grep può restituire 1 senza interrompere lo script, perché il fallimento è usato per decidere il ramo del if. Questo è corretto dal punto di vista della shell, ma per chi legge lo script può sembrare una violazione della promessa di set -e.
2. Pipeline
In una pipeline, per default Bash considera il risultato dell’ultimo comando della catena. Questo significa che un errore nei comandi iniziali può passare inosservato se l’ultimo comando riesce.
#!/usr/bin/env bash
set -e grep 'pattern' file.txt | wc -l echo 'potrebbe proseguire anche se grep fallisce'Se grep non trova nulla, restituisce 1; ma la pipeline può comunque risultare “ok” se wc termina correttamente. Per intercettare il fallimento di tutti i componenti della pipeline serve set -o pipefail.
set -euo pipefailQuesta combinazione è comune negli script robusti, ma va capita: -e gestisce l’uscita su errore, -u blocca l’uso di variabili non inizializzate, pipefail fa emergere gli errori nelle pipeline. Sono tre problemi diversi, spesso confusi tra loro.
3. Comandi preceduti da !
Il negatore ! inverte il codice di uscita. È utile, ma può nascondere il significato di un fallimento rispetto a set -e.
#!/usr/bin/env bash
set -e if ! curl -fsS https://example.com/health; then echo 'servizio non raggiungibile'
fiQui il fallimento di curl è gestito in modo esplicito. È una delle forme corrette di uso di Bash: il comando può fallire, ma lo script decide cosa fare invece di delegarlo a una semantica implicita.
4. Sostituzione di comando
Le sostituzioni di comando con $(...) sono un altro punto delicato. Un errore interno non sempre si comporta come ti aspetti, soprattutto se il valore viene usato in una assegnazione o in una costruzione più complessa.
#!/usr/bin/env bash
set -e name=$(cat /non/esiste)
echo "$name"In molti casi lo script termina, ma non conviene affidarsi a questo comportamento per la logica applicativa. Se il fallimento della lettura è importante, conviene testarlo apertamente e gestire l’errore vicino al punto in cui nasce.
Perché molti script “sembrano sicuri” e poi non lo sono
Il difetto classico è usare set -e come unico meccanismo di controllo e poi scrivere script pieni di eccezioni implicite. Il risultato è un codice che appare rigoroso ma si comporta in modo incoerente. Il problema emerge soprattutto nei deploy automatici e nei job di manutenzione, dove una semantica ambigua può trasformarsi in stato parziale, dati incompleti o rollback mancati.
Un esempio pratico: uno script crea un backup, aggiorna una configurazione e riavvia un servizio. Se il backup fallisce ma il comando è dentro una pipeline o dentro una costruzione gestita male, lo script può continuare e applicare il cambio senza un punto di ripristino affidabile. Qui set -e non basta: serve verificare esplicitamente ogni fase critica.
Il pattern corretto: error handling esplicito
La forma più robusta non è “attiva set -e e spera”, ma “usa set -e come rete di ultima istanza e scrivi controlli chiari dove serve”. In pratica:
Abilita set -euo pipefail all’inizio dello script, se il tuo contesto Bash lo supporta e se hai verificato che non rompe logiche esistenti.
Controlla esplicitamente i passaggi critici con if, || e funzioni di validazione.
Usa variabili e funzioni con nomi che rendano chiaro il punto di fallimento.
Logga il contesto prima di operazioni distruttive o irreversibili.
Prevedi rollback o almeno una via di uscita sicura.
Un esempio più solido:
#!/usr/bin/env bash
set -euo pipefail backup_file=/var/backups/app.conf.$(date +%F-%H%M%S) cp /etc/app/app.conf "$backup_file" if ! grep -q '^enabled=true' /etc/app/app.conf; then echo 'config non valida: manca enabled=true' >&2 exit 1
fi systemctl reload app-serviceQui il backup è esplicito, il controllo è leggibile, il reload avviene solo se la validazione passa. Il valore di set -e è quello di fermare eventuali errori non previsti, non di sostituire la logica applicativa.
set -e dentro le funzioni
Le funzioni Bash ereditano il comportamento della shell, ma anche qui ci sono sfumature. Se una funzione contiene un comando fallito in un contesto “protetto” da Bash, lo script può non terminare come ci si aspetta. Per questo motivo le funzioni critiche dovrebbero restituire codici di uscita chiari e documentati, invece di affidarsi alla semantica implicita della shell.
deploy_config() { cp app.conf /etc/app/app.conf systemctl reload app-service
} if ! deploy_config; then echo 'deploy fallito' >&2 exit 1
fiIn questo schema, il punto di controllo è il ritorno della funzione. È più leggibile, più testabile e meno dipendente dai dettagli interni di set -e.
Come testare davvero il comportamento
Se vuoi capire come si comporta uno script, non basta leggerlo: va eseguito con errori intenzionali. Questo è il modo più veloce per evitare false sicurezze.
Inserisci un comando che fallisca in un punto semplice, per esempio false o una lettura di file inesistente.
Verifica se lo script si ferma dove ti aspetti.
Prova lo stesso comando dentro un if, una pipeline e una sostituzione di comando.
Confronta il comportamento con e senza set -o pipefail.
Esempio minimo di prova:
#!/usr/bin/env bash
set -e echo 'prima'
false
echo 'dopo'Il testo dopo non deve comparire. Se compare, lo script non è stato eseguito come pensavi o c’è una struttura che ha neutralizzato -e.
Quando conviene non usare set -e
Ci sono script in cui set -e complica più di quanto aiuti. Succede quando l’errore è un evento normale e frequente, da gestire nel flusso. Per esempio, script che scandiscono directory, confrontano stati, verificano servizi o fanno discovery di risorse devono aspettarsi fallimenti parziali come parte del comportamento normale.
In questi casi è spesso meglio lasciare set -e disattivato e controllare ogni comando con if, case o funzioni di supporto. Il vantaggio è che il lettore capisce subito quali errori sono attesi e quali invece sono anomali.
Scelta pratica per script reali
Se devi scrivere uno script operativo per ambienti Linux, la scelta ragionevole è questa: usa set -euo pipefail nei flussi lineari e prevedibili; evita di farci affidamento nei punti in cui Bash ha semantiche speciali; tratta ogni operazione irreversibile come un passo che richiede verifica e, se possibile, rollback.
In altre parole, set -e è utile per ridurre il rumore, non per eliminare il problema. Il vero salto di qualità sta nel progettare script che falliscano in modo leggibile, vicino alla causa, e che non trasformino un errore locale in un disastro silenzioso a valle.
Se vuoi un criterio rapido: quando un comando fallito deve fermare tutto, scrivilo in modo che sia evidente; quando un fallimento è atteso, gestiscilo esplicitamente; quando non sei sicuro, testa il caso con un input rotto prima di fidarti dello script in produzione.
Commenti (0)
Nessun commento ancora.
Segnala contenuto
Elimina commento
Eliminare definitivamente questo commento?
L'azione non si può annullare.