Guida

Script bash per principianti: primo script, variabili, condizioni, cicli e cron

Aggiornato: · Skillaria.Top

Uno script bash è un file di testo con comandi della shell che inizia con la riga #!/bin/bash, riceve il permesso di esecuzione con chmod +x e si avvia come ./script.sh. Negli script si usano variabili, argomenti $1 e $2, condizioni if, cicli for e while, mentre l'esecuzione periodica si configura con crontab -e.

Uno script bash è un normale file di testo con i comandi che altrimenti digiteresti uno alla volta nel terminale. Lo script inizia con la riga shebang #!/bin/bash, riceve il permesso di esecuzione con chmod +x e si avvia come ./script.sh. Questa guida per principianti porta dallo script di una riga fino all'esecuzione pianificata: variabili, argomenti, condizioni, cicli, gestione degli errori e cron.

Il primo script bash in un minuto

Crea il file con un editor qualsiasi, ad esempio nano:

nano hello.sh
#!/bin/bash
# Primo script: saluto e qualche informazione sul sistema
echo "Ciao, $USER!"
echo "Oggi è: $(date +%Y-%m-%d)"
echo "Ti trovi in: $(pwd)"

Salva (Ctrl+O, Invio, Ctrl+X), concedi il permesso di esecuzione e avvialo:

$ chmod +x hello.sh
$ ./hello.sh
Ciao, alice!
Oggi è: 2026-10-12
Ti trovi in: /home/alice

Le righe che iniziano con # sono commenti. $(comando) inserisce direttamente nella riga l'output di un comando.

Shebang: perché serve la prima riga

La riga #!/bin/bash indica al sistema quale interprete usare per eseguire il file. Senza di essa lo script viene eseguito con la shell che capita di trovare, e in alcuni sistemi /bin/sh non è bash ma il più semplice dash, dove alcune costruzioni non funzionano.

Spesso si trova anche la variante #!/usr/bin/env bash: cerca bash nel PATH, utile se bash non si trova in /bin.

Come eseguire lo script e perché ./

Ci sono due modi:

  • ./hello.sh — richiede lo shebang e il permesso x. Il prefisso ./ è obbligatorio, perché la directory corrente non fa parte del PATH.
  • bash hello.sh — non serve il permesso di esecuzione, perché l'interprete è indicato esplicitamente.

Se senza ./ vedi command not found e senza chmod +x vedi Permission denied, sono proprio queste le due cause. Per richiamare i propri script per nome da qualsiasi posizione, mettili in ~/bin o in /usr/local/bin.

Variabili

Una variabile si definisce senza spazi attorno al simbolo = e si legge con il prefisso $:

#!/bin/bash
name="backup"
dir="/var/backups"
today=$(date +%F)

echo "File: ${dir}/${name}-${today}.tar.gz"

Le parentesi graffe separano il nome della variabile dal testo circostante. La riga name = "backup" con gli spazi è sbagliata: bash proverebbe a eseguire un comando chiamato name.

Le virgolette contano

Tra virgolette doppie le variabili vengono sostituite, tra virgolette singole no. Metti quasi sempre le variabili tra virgolette doppie, altrimenti un nome di file con spazi si divide in due argomenti.

file="rapporto finale.txt"
rm $file      # sbagliato: prova a eliminare «rapporto» e «finale.txt»
rm "$file"    # corretto
echo '$HOME'  # stampa letteralmente $HOME
echo "$HOME"  # stampa /home/alice

Variabili d'ambiente

Variabili come $HOME, $USER, $PATH sono impostate dal sistema. Una variabile propria si passa ai processi figli con export. Le impostazioni permanenti si scrivono in ~/.bashrc o ~/.profile.

Argomenti dello script

I valori passati all'avvio sono disponibili come $1, $2 e così via.

ead>
VariabileContenuto
$0nome dello script
$1, $2…primo, secondo argomento
$#numero di argomenti
"$@"tutti gli argomenti, separatamente
$?codice di uscita dell'ultimo comando
$$PID dello script in esecuzione
#!/bin/bash
echo "Lo script $0 ha ricevuto $# argomenti"
echo "Primo: $1"
$ ./args.sh nginx 80
Lo script ./args.sh ha ricevuto 2 argomenti
Primo: nginx

L'input dell'utente si legge con il comando read: read -p "Inserisci il nome: " name.

Condizioni if

Ogni comando restituisce un codice di uscita: 0 indica successo, qualsiasi altro valore un errore. L'istruzione if controlla proprio questo codice. Per i confronti si usa [[ ... ]], e gli spazi dentro le parentesi sono obbligatori.

#!/bin/bash
if [[ $# -eq 0 ]]; then
    echo "Uso: $0 <directory>"
    exit 1
fi

if [[ -d "$1" ]]; then
    echo "La directory $1 esiste"
elif [[ -f "$1" ]]; then
    echo "$1 è un file, non una directory"
else
    echo "$1 non trovato"
fi
ead>
VerificaSignificato
-f fileil file regolare esiste
-d dirla directory esiste
-e pathil percorso esiste
-x fileil file è eseguibile
-z "$var" / -n "$var"la stringa è vuota / non vuota
"$a" == "$b", !=confronto tra stringhe
-eq -ne -lt -le -gt -geconfronto tra numeri

Si può verificare anche direttamente l'esito di un comando:

if systemctl is-active --quiet nginx; then
    echo "nginx è attivo"
else
    echo "nginx è fermo"
fi

Per molte alternative è comodo case:

case "$1" in
    start)   echo "Avvio in corso" ;;
    stop)    echo "Arresto in corso" ;;
    *)       echo "Comando sconosciuto: $1"; exit 1 ;;
esac

Cicli for e while

for: scorrere un elenco

for host in web1 web2 db1; do
    echo "Controllo $host"
    ping -c 1 -W 1 "$host" > /dev/null && echo "  raggiungibile" || echo "  non raggiungibile"
done

for file in /var/log/*.log; do
    echo "$file: $(wc -l < "$file") righe"
done

for i in {1..5}; do
    echo "Tentativo $i"
done

while: finché la condizione è vera

Il classico esempio è la lettura di un file riga per riga:

while read -r user; do
    echo "Utente: $user"
done < users.txt

E l'attesa che un servizio si avvii:

count=0
until curl -sf http://localhost:8080/health > /dev/null; do
    count=$((count + 1))
    if [[ $count -ge 10 ]]; then
        echo "Il servizio non si è avviato"
        exit 1
    fi
    sleep 3
done
echo "Servizio pronto"

L'aritmetica in bash si scrive tra doppie parentesi: $((a + b)). Funziona solo con numeri interi.

Funzioni

Il codice ripetuto si raccoglie in funzioni. Gli argomenti dentro una funzione sono ancora $1, $2, e local impedisce alla variabile di uscire dalla funzione.

log() {
    local level="$1"; shift
    echo "$(date '+%F %T') [$level] $*"
}

log INFO "Avvio del backup"
log ERROR "Spazio su disco esaurito"

Gestione degli errori: set -euo pipefail

Per impostazione predefinita bash continua dopo un comando fallito, e uno script di backup può terminare con «successo» senza aver copiato nulla. Per questo all'inizio degli script si mette la modalità severa:

#!/bin/bash
set -euo pipefail
  • -e — fermati al primo errore.
  • -u — considera un errore l'uso di una variabile non definita (evita che rm -rf "$DIR/" venga eseguito con DIR vuota).
  • -o pipefail — una pipeline fallisce se fallisce una qualsiasi delle sue parti.

Per il debug esegui lo script con bash -x script.sh: bash stampa ogni comando prima di eseguirlo. Per trovare gli errori tipici prima dell'esecuzione è utile lo strumento shellcheck.

Esempio pratico: script di backup

#!/bin/bash
set -euo pipefail

SRC="${1:-/var/www}"
DST="/var/backups/site"
KEEP_DAYS=7
STAMP=$(date +%F_%H-%M)

mkdir -p "$DST"
tar -czf "$DST/site-$STAMP.tar.gz" -C "$(dirname "$SRC")" "$(basename "$SRC")"
echo "Archivio creato: $DST/site-$STAMP.tar.gz"

# elimina gli archivi più vecchi di KEEP_DAYS giorni
find "$DST" -name "site-*.tar.gz" -mtime +"$KEEP_DAYS" -delete

La scrittura ${1:-/var/www} significa: «primo argomento; se manca, /var/www». Salva lo script come /usr/local/bin/backup-site.sh e rendilo eseguibile: sudo chmod +x /usr/local/bin/backup-site.sh.

Esecuzione pianificata: cron

Cron è il servizio che esegue i comandi a orari prestabiliti. Le attività dell'utente si modificano con crontab -e e si elencano con crontab -l; quelle di root con sudo crontab -e.

Una riga di cron contiene cinque campi temporali seguiti dal comando:

# minuto ora giorno_del_mese mese giorno_della_settimana comando
30 2 * * * /usr/local/bin/backup-site.sh >> /var/log/backup.log 2>&1
ead>
PianificazioneQuando viene eseguito
*/5 * * * *ogni 5 minuti
0 * * * *all'inizio di ogni ora
30 2 * * *ogni giorno alle 02:30
0 9 * * 1-5nei giorni feriali alle 09:00
0 3 1 * *il primo giorno di ogni mese alle 03:00
@rebootuna volta dopo l'avvio

Lo script funziona a mano ma non in cron

  • Ambiente diverso. Cron esegue i task con un PATH minimo e senza il tuo ~/.bashrc. Scrivi i percorsi completi di comandi e file, oppure imposta PATH all'inizio della crontab.
  • Directory corrente diversa. Il task parte dalla home, non dalla cartella dello script. Usa percorsi assoluti.
  • Il carattere %. Nella crontab il simbolo % significa «a capo», quindi date +%F direttamente nella riga va scritto come \%. È più semplice spostare il comando nello script.
  • L'output si perde. Reindirizza l'output in un log (>> file 2>&1), altrimenti non vedrai gli errori. Le esecuzioni di cron si consultano con journalctl -u cron (nelle distribuzioni tipo RHEL il servizio si chiama crond).

Sui sistemi con systemd, un'alternativa a cron sono i timer di systemd: scrivono l'output nel journal e possono recuperare le esecuzioni perse.

Conclusioni

Uno script bash per principianti è un file che inizia con lo shebang #!/bin/bash, ha il permesso di esecuzione e contiene una sequenza di comandi. Aggiungi variabili tra virgolette, il controllo degli argomenti con if, i cicli for e while e la modalità severa set -euo pipefail: otterrai uno strumento di automazione affidabile, che cron eseguirà all'orario stabilito. Script, ambiente e cron sono trattati nel corso con esercizi pratici.

Domande frequenti

Qual è la differenza tra bash e sh?

sh è la shell POSIX standard, mentre bash è una versione estesa con array, doppie parentesi e altre comodità. Uno script che usa costrutti di bash va eseguito con bash, non con sh.

Quale estensione deve avere uno script bash?

Linux non guarda l'estensione: l'interprete è stabilito dallo shebang. <code>.sh</code> è solo una convenzione per rendere evidente che si tratta di uno script.

Come passo una password allo script in modo sicuro?

Non passarla come argomento, perché gli argomenti sono visibili agli altri utenti tramite <code>ps</code>. Usa una variabile d'ambiente letta da un file con permessi 600, oppure <code>read -s</code> per inserirla senza mostrarla.

Come verifico che lo script sia eseguito come root?

Confronta l'ID utente con zero: <code>if [[ $EUID -ne 0 ]]; then echo "Serve root"; exit 1; fi</code>.

Come faccio a far continuare lo script dopo la disconnessione SSH?

Usa <code>nohup ./script.sh &</code>, oppure una sessione tmux o screen. Per un funzionamento stabile trasforma lo script in un servizio systemd.

Impara l’argomento in video

Questo argomento è trattato in modo approfondito, con pratica, nel corso «Linux: с нуля до уверенного администрирования».

Vai al corso