Spcnet.it Notizie geek e OSS/Linux. Su Telegram: InfoSec ITA Notizie
Home > Articolo > Bash scripting per sistemisti: dai fondamentali agli script di produzione pronti per cron
Bash scripting per sistemisti: dai fondamentali agli script di produzione pronti per cron

Perché la maggior parte degli script bash “funziona sulla mia macchina” e poi fallisce in produzione

Chiunque amministri sistemi Linux ha almeno uno script bash scritto in dieci minuti per risolvere un problema urgente, che poi è finito silenziosamente in produzione tramite un cron job. Il problema è che uno script funzionale non è automaticamente uno script affidabile: la differenza si vede quando lo script gira senza terminale, senza nessuno che guarda l’output, magari in concorrenza con un’altra esecuzione dello stesso job.

In questo articolo ripassiamo rapidamente le basi della scrittura di script bash, ma ci concentriamo soprattutto su ciò che separa uno script “da developer” da uno pronto per l’automazione in un ambiente sistemistico reale: gestione degli errori, logging, locking e debugging.

Le fondamenta, in breve

Uno script bash è un file di testo con una sequenza di comandi che la shell esegue in ordine. La prima riga, lo shebang, indica al kernel quale interprete usare:

#!/usr/bin/env bash
echo "Hello, world"

Usare /usr/bin/env bash invece di /bin/bash hardcoded rende lo script più portabile su sistemi dove bash non risiede nel path standard (tipico su alcune distribuzioni minimali o container). Reso eseguibile con chmod +x script.sh, lo script si lancia con ./script.sh.

Variabili e quoting

Una convenzione diffusa negli ambienti Linux è usare il maiuscolo per variabili globali/di configurazione e il minuscolo per variabili locali, evitando di sovrascrivere variabili di sistema come PATH, HOME o USER. La regola più importante, spesso ignorata, è: quotare sempre le variabili.

SITE="miosito.it"
DATE=$(date +%Y-%m-%d)
echo "Backup di $SITE il $DATE"

Senza le virgolette, un valore contenente spazi o caratteri speciali può spezzare la logica dello script in modi difficili da diagnosticare, specialmente con percorsi di file.

Argomenti e parametri posizionali

if [ $# -lt 1 ]; then
  echo "Uso: $0 <file>"
  exit 1
fi
FILE="$1"

Per script più complessi, destinati a essere invocati con flag (-v, --dry-run), conviene usare getopts per le opzioni brevi, che gestisce automaticamente la validazione e i messaggi di errore standard, evitando il parsing manuale di $@ con cicli while annidati e propensi a bug.

Dove gli script “da sviluppatore” falliscono in produzione

1. Modalità strict e i suoi limiti reali

La pratica più citata è attivare la modalità rigorosa in testa allo script:

set -euo pipefail
  • -e: esce immediatamente se un comando termina con stato diverso da zero
  • -u: tratta le variabili non definite come errore
  • -o pipefail: propaga il fallimento anche a metà di una pipeline, non solo sull’ultimo comando

È una buona base, ma va usata sapendo dove non funziona: set -e viene ignorato all’interno delle condizioni di if, nelle catene &&/|| e in certi contesti di funzione. Inoltre pipefail può creare falsi positivi con comandi come grep (che esce con stato diverso da zero se non trova corrispondenze) o head (che chiude il descrittore a monte, generando l’exit code 141 su SIGPIPE). Se il vostro script fa comando | grep pattern dentro uno script in pipefail, uno “zero risultati” legittimo può far abortire l’intero job: va gestito esplicitamente, ad esempio con grep pattern || true quando l’assenza di match è un esito accettabile.

2. Logging: se gira da cron, nessuno vede lo stdout

Uno script lanciato da terminale che scrive con echo è inutile quando lo stesso script gira da cron: l’output, se non rediretto, o finisce in una mail di sistema che nessuno legge, o si perde. La soluzione minima è una funzione di logging centralizzata:

LOGFILE="/var/log/watchdog.log"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOGFILE"
}

log "Script avviato"

Questo pattern, combinato con local per le variabili interne alle funzioni (evitando l’inquinamento dello scope globale), è alla base di quasi ogni script di automazione che vale la pena mettere in cron.

3. Evitare esecuzioni concorrenti con flock

Un errore classico: un job schedulato ogni minuto che, in caso di rallentamento (I/O lento, rete, dipendenza esterna), parte una seconda volta prima che la prima istanza sia terminata. Il modo standard per evitarlo su Linux è flock, che usa un file descriptor come lock a livello di kernel:

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

LOCK_FILE="/run/lock/$(basename "$0").lock"
exec 200>"$LOCK_FILE"

if ! flock -n 200; then
  echo "Esecuzione precedente ancora attiva, salto questo ciclo" >&2
  exit 0
fi

echo "Lock acquisito, avvio il job reale"
# ... logica dello script ...

Il flag -n rende l’acquisizione non bloccante: se un’altra istanza detiene già il lock, lo script esce subito invece di accodarsi. Se preferite attendere fino a un certo timeout invece di saltare il ciclo, -w 30 attende fino a 30 secondi prima di arrendersi. È preferibile salvare il lock file sotto /run/lock piuttosto che /tmp, che su molte distribuzioni viene ripulito periodicamente anche mentre un processo è in esecuzione. In alternativa, il locking si può applicare direttamente dal crontab senza toccare lo script:

*/1 * * * * /usr/bin/flock -n /run/lock/sync.lock /usr/local/bin/sync.sh

4. Un watchdog completo, con logica di retry

Mettendo insieme logging, controllo di stato e gestione dell’errore, un caso d’uso ricorrente per un sistemista è un watchdog che verifica un servizio systemd e tenta un riavvio in caso di failure:

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

SERVICE="nginx"
LOGFILE="/var/log/watchdog.log"

log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOGFILE"
}

if ! systemctl is-active --quiet "$SERVICE"; then
  log "$SERVICE risulta down. Tento il riavvio."
  systemctl restart "$SERVICE"
  if systemctl is-active --quiet "$SERVICE"; then
    log "$SERVICE riavviato correttamente."
  else
    log "$SERVICE non è ripartito: serve intervento manuale."
    exit 1
  fi
fi

Notate l’uso di exit 1 nel caso di fallimento definitivo: se questo script è invocato da un sistema di monitoraggio esterno (Nagios, Zabbix, un semplice controllo su exit code), un codice di uscita diverso da zero è ciò che permette di trasformare un log dimenticato in un alert reale.

Debug: strumenti che risparmiano ore

  • bash -n script.sh — verifica solo la sintassi, senza eseguire nulla
  • bash -x script.sh — traccia ogni comando con le variabili espanse, utile per capire cosa succede realmente riga per riga
  • set -x / set +x — attiva/disattiva il tracing solo su una porzione dello script
  • shellcheck script.sh — analisi statica che individua variabili non quotate, condizioni malformate e pipe inutili che bash -n non può rilevare; è disponibile nei repository di tutte le principali distribuzioni ed è ormai lo standard de facto prima di qualsiasi merge in un repository di script di automazione

Conclusione

Le basi della sintassi bash si imparano in un pomeriggio; la differenza tra uno script giocattolo e uno adatto alla produzione sta tutta negli aspetti che spesso vengono aggiunti solo dopo il primo incidente in produzione: modalità strict usata consapevolmente, logging pensato per l’esecuzione non interattiva, locking per evitare sovrapposizioni, e una pipeline minima di verifica (shellcheck più test manuale con bash -x) prima di schedulare qualsiasi cosa su cron o systemd timer. Vale la pena trattare gli script di automazione con lo stesso rigore riservato al codice applicativo: revisione, versionamento e, quando possibile, un minimo di test automatizzati.

Fonte originale: Linux Bash Scripting: Writing Your First Script, LinuxBlog.io.

Condividi: Twitter  |  Facebook  |  LinkedIn
Unisciti alla discussione

Questo è un blog del Fediverso: puoi trovare questo articolo ovunque con @blog@insicurezzadigitale.com e ogni commento/risposta apparirà qui sotto.

Se vuoi commentare su Bash scripting per sistemisti: dai fondamentali agli script di produzione pronti per cron, utilizza la discussione sul Forum.

>> forum community