Uno script Bash funziona anche se "improvvisato", ma quando cresce diventa fragile. Una struttura chiara ti fa risparmiare tempo su bug, modifiche e manutenzione.
1. Perche' strutturare bene uno script
- Piu' facile da leggere e modificare
- Meno errori con spazi, variabili vuote e path strani
- Debug piu' semplice
- Riutilizzo di funzioni in altri script
2. Template base consigliato
Un buon punto di partenza per script Bash moderni:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="/tmp/${SCRIPT_NAME%.sh}.log"
usage() {
cat <<'EOF'
Uso: script.sh [opzioni]
Opzioni:
-h, --help Mostra questo help
-v, --verbose Output piu' dettagliato
EOF
}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "$LOG_FILE"
}
die() {
log "ERRORE: $*"
exit 1
}
main() {
log "Avvio script: $SCRIPT_NAME"
# logica principale qui
}
main "$@"
set -euo pipefail aiuta a intercettare errori presto, ma va usato con consapevolezza (specialmente con pipeline, comandi opzionali e test).
3. Variabili e costanti
Regole pratiche
- Usa nomi chiari:
backup_dir,source_file - Quote quasi sempre le espansioni:
"$var" - Per costanti usa maiuscolo:
DEFAULT_PORT=22 - Per array usa Bash array, non stringhe concatenate
DEFAULT_PORT=22
backup_dir="/var/backups/app"
files=("config.yml" "db.sql" "notes.txt")
for f in "${files[@]}"; do
printf 'File: %s\n' "$f"
done
Evita
for f in $(ls ...): rompe facilmente con spazi e caratteri speciali nei nomi file.
4. Funzioni e organizzazione del codice
Separa il codice in funzioni piccole con una responsabilita' chiara.
check_requirements() {
command -v rsync >/dev/null 2>&1 || die "rsync non trovato"
}
run_backup() {
local src="$1"
local dst="$2"
rsync -av --delete "$src/" "$dst/"
}
main() {
check_requirements
run_backup "/etc" "/backup/etc"
}
Ordine consigliato nel file:
- Shebang + opzioni shell
- Costanti/variabili globali
- Funzioni utility (
log,die,usage) - Funzioni di business
main- Chiamata finale:
main "$@"
5. Argomenti, opzioni e help
Gestisci gli argomenti in modo esplicito. Esempio semplice con case:
VERBOSE=0
TARGET_DIR=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-v|--verbose)
VERBOSE=1
shift
;;
-t|--target)
[[ $# -lt 2 ]] && die "Valore mancante per $1"
TARGET_DIR="$2"
shift 2
;;
*)
die "Argomento non riconosciuto: $1"
;;
esac
done
[[ -z "$TARGET_DIR" ]] && die "Devi specificare --target"
Per script piu' complessi puoi usare
getopts (opzioni corte) o un parser dedicato, ma il pattern con while + case e' spesso sufficiente.
6. Gestione errori (best practice reali)
Controlla le dipendenze prima
command -v curl >/dev/null 2>&1 || die "curl non installato"
command -v jq >/dev/null 2>&1 || die "jq non installato"
Non ignorare i codici di uscita
if ! cp "$src" "$dst"; then
die "Copia fallita: $src -> $dst"
fi
Valori di default sicuri
: "${TMPDIR:=/tmp}"
: "${BACKUP_KEEP_DAYS:=7}"
Evita effetti collaterali pericolosi
Prima di fare operazioni distruttive (
rm, mv, sync), stampa il target e valida che non sia vuoto:
[[ -n "${target_dir:-}" ]] || die "target_dir vuota"
[[ "$target_dir" != "/" ]] || die "Rifiutato: target_dir e' /"
7. Logging semplice ma utile
Usa funzioni dedicate per log con livelli.
log_info() { printf '[INFO] %s\n' "$*"; }
log_warn() { printf '[WARN] %s\n' "$*" >&2; }
log_error() { printf '[ERROR] %s\n' "$*" >&2; }
log_info "Backup avviato"
log_warn "Spazio disco basso"
log_error "Connessione fallita"
Se vuoi salvare anche su file:
exec > >(tee -a "$LOG_FILE")
exec 2>&1
Questo redirige stdout/stderr a
tee. Utile per script di manutenzione, meno adatto se vuoi output "pulito" per parsing automatico.
8. Trap e cleanup
Quando crei file temporanei o lockfile, usa trap per pulire anche in caso di errore/interruzione.
TMP_FILE="$(mktemp)"
cleanup() {
rm -f "$TMP_FILE"
}
trap cleanup EXIT
trap 'echo "Interrotto"; exit 130' INT TERM
9. Permessi e sicurezza
- Rendi eseguibile solo se serve:
chmod +x script.sh - Verifica se richiede root all'inizio dello script
- Non salvare password in chiaro nello script
- Usa
umaskse generi file sensibili
require_root() {
[[ "${EUID:-$(id -u)}" -eq 0 ]] || die "Esegui come root"
}
Se usi input esterno (argomenti, file, output di comandi), trattalo come non fidato: quota sempre e valida il formato.
10. Debug, lint e test
Debug rapido
bash -x ./script.sh --target /tmp/test
bash -n ./script.sh
-x: traccia i comandi eseguiti-n: controlla solo la sintassi
ShellCheck (fortemente consigliato)
shellcheck script.sh
Ti segnala errori comuni, quote mancanti, uso errato di variabili e pattern fragili.
Mini checklist finale
[ ] Shebang corretto (#!/usr/bin/env bash)
[ ] set -euo pipefail (se adatto allo script)
[ ] Variabili quotate
[ ] Argomenti validati
[ ] Error handling con die()/exit code
[ ] Trap cleanup se uso file temporanei
[ ] bash -n e shellcheck eseguiti
Template finale riutilizzabile
#!/usr/bin/env bash
set -euo pipefail
usage() { echo "Uso: $0 --target DIR"; }
die() { echo "ERRORE: $*" >&2; exit 1; }
TARGET=""
while [[ $# -gt 0 ]]; do
case "$1" in
--target) TARGET="${2:-}"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) die "Argomento non riconosciuto: $1" ;;
esac
done
[[ -n "$TARGET" ]] || die "Specifica --target"
[[ -d "$TARGET" ]] || die "Directory non trovata: $TARGET"
main() {
echo "Operazione su: $TARGET"
}
main "$@"