
Gli script Bash sono uno degli strumenti più efficaci per automatizzare attività su sistemi GNU/Linux. Su distribuzioni come Ubuntu, Fedora e openSUSE permettono di semplificare operazioni ripetitive, gestire file, controllare servizi, orchestrare comandi di sistema, creare procedure di backup, analizzare log e costruire piccoli strumenti amministrativi senza introdurre dipendenze complesse.
Bash, acronimo di Bourne Again SHell, è una shell testuale e un linguaggio di scripting. Nella pratica quotidiana consente di eseguire comandi singoli da terminale oppure di raccoglierli in file eseguibili, creando procedure riutilizzabili. L’approccio è particolarmente utile in ambienti server, workstation di sviluppo, laboratori didattici e sistemi desktop GNU/Linux.
Cos’è uno script Bash
Uno script Bash è un file di testo contenente una sequenza di comandi che la shell interpreta ed esegue. Può essere usato per automatizzare operazioni semplici, come la creazione di directory, oppure procedure più complesse, come la verifica dello stato dei servizi, la rotazione di backup o l’elaborazione di file di log.
Un primo script minimale può essere creato con un normale editor di testo. La prima riga, detta shebang, indica al sistema quale interprete utilizzare.
#!/usr/bin/env bash echo "Benvenuto nel primo script Bash"
Salvando il file, ad esempio con nome primo-script.sh, è necessario renderlo eseguibile prima di lanciarlo direttamente.
chmod +x primo-script.sh ./primo-script.sh
L’uso di /usr/bin/env bash nello shebang è generalmente preferibile perché cerca Bash nel percorso dell’ambiente corrente, aumentando la portabilità dello script tra distribuzioni diverse.
Prerequisiti su Ubuntu, Fedora e openSUSE
Bash è installato di default nella maggior parte delle distribuzioni GNU/Linux, incluse Ubuntu, Fedora e openSUSE. È comunque possibile verificarne la presenza e la versione dal terminale.
bash --version which bash
Per modificare gli script è sufficiente un editor testuale. In ambiente terminale sono comuni nano, vim o micro; in ambiente grafico si possono usare editor come Gedit, Kate, VS Code o altri strumenti equivalenti.
# Ubuntu / Debian sudo apt update sudo apt install nano vim # Fedora sudo dnf install nano vim # openSUSE sudo zypper install nano vim
Comandi base utili per iniziare
Prima di scrivere script complessi è importante conoscere i principali comandi di navigazione, gestione file e consultazione del sistema. Questi comandi rappresentano la base operativa su cui si costruiscono molte automazioni Bash.
pwd # Mostra la directory corrente ls # Elenca file e directory ls -lah # Elenca con dettagli, file nascosti e dimensioni leggibili cd /percorso # Cambia directory mkdir nuova_cartella touch file.txt cp file.txt copia.txt mv copia.txt archivio.txt rm archivio.txt cat file.txt less file.txt head file.txt tail file.txt
In uno script, questi comandi possono essere combinati per creare procedure ripetibili. È buona pratica usare percorsi assoluti o variabili ben definite quando lo script deve essere eseguito in contesti diversi.
Variabili e sostituzione dei valori
Le variabili permettono di memorizzare valori temporanei, percorsi, nomi di file, opzioni o risultati di comandi. In Bash non devono esserci spazi attorno al simbolo di assegnazione.
#!/usr/bin/env bash NOME="Mario" DISTRIBUZIONE="GNU/Linux" echo "Ciao $NOME" echo "Stai usando $DISTRIBUZIONE"
Le variabili possono contenere anche il risultato di un comando. Questa tecnica è detta command substitution.
#!/usr/bin/env bash DATA_CORRENTE=$(date +"%Y-%m-%d") HOSTNAME_SISTEMA=$(hostname) echo "Data: $DATA_CORRENTE" echo "Sistema: $HOSTNAME_SISTEMA"
Quando una variabile può contenere spazi o caratteri speciali, è opportuno racchiuderla tra doppi apici durante l’uso.
FILE="documento importante.txt" echo "Creo il file: $FILE" touch "$FILE"
Parametri passati allo script
Uno script può ricevere argomenti dalla riga di comando. Bash li espone tramite variabili speciali: il primo argomento è rappresentato da $1, il secondo da $2 e così via. Il numero totale degli argomenti è disponibile tramite $#.
#!/usr/bin/env bash echo "Nome dello script: $0" echo "Primo argomento: $1" echo "Secondo argomento: $2" echo "Numero totale di argomenti: $#"
Esecuzione dello script con parametri:
./script.sh Ubuntu Fedora
È buona norma controllare sempre che gli argomenti necessari siano stati forniti, soprattutto quando lo script deve modificare file o configurazioni.
#!/usr/bin/env bash if [ $# -lt 1 ]; then echo "Uso: $0 <nome_file>" exit 1 fi FILE="$1" echo "File indicato: $FILE"
Condizioni con if, test e operatori
Le strutture condizionali consentono allo script di prendere decisioni. Sono fondamentali per verificare l’esistenza di file, confrontare stringhe, controllare valori numerici o gestire errori.
#!/usr/bin/env bash FILE="/etc/os-release" if [ -f "$FILE" ]; then echo "Il file $FILE esiste" else echo "Il file $FILE non esiste" fi
Alcuni operatori comuni per i file:
-f Verifica se è un file regolare -d Verifica se è una directory -e Verifica se il percorso esiste -r Verifica se il file è leggibile -w Verifica se il file è scrivibile -x Verifica se il file è eseguibile
Per i confronti numerici si usano operatori specifici.
#!/usr/bin/env bash NUMERO=10 if [ "$NUMERO" -gt 5 ]; then echo "Il numero è maggiore di 5" fi
-eq Uguale -ne Diverso -gt Maggiore -ge Maggiore o uguale -lt Minore -le Minore o uguale
Uso di case per gestire scelte multiple
Quando uno script deve gestire più opzioni, la struttura case è spesso più leggibile di una lunga sequenza di condizioni if.
#!/usr/bin/env bash
AZIONE="$1"
case "$AZIONE" in
start)
echo "Avvio del servizio"
;;
stop)
echo "Arresto del servizio"
;;
restart)
echo "Riavvio del servizio"
;;
status)
echo "Verifica dello stato"
;;
*)
echo "Uso: $0 {start|stop|restart|status}"
exit 1
;;
esac
Cicli for, while e lettura di file
I cicli permettono di ripetere operazioni su liste di valori, file, directory o righe di testo. Sono uno degli strumenti più utilizzati negli script operativi.
#!/usr/bin/env bash for SISTEMA in Ubuntu Fedora openSUSE; do echo "Distribuzione: $SISTEMA" done
Un ciclo può essere applicato ai file presenti in una directory.
#!/usr/bin/env bash for FILE in *.log; do echo "Analizzo il file: $FILE" tail -n 5 "$FILE" done
Per leggere un file riga per riga si usa spesso while read.
#!/usr/bin/env bash FILE_LISTA="utenti.txt" while IFS= read -r RIGA; do echo "Utente: $RIGA" done < "$FILE_LISTA"
L’uso di IFS= e read -r evita alterazioni indesiderate degli spazi e dei caratteri di escape presenti nelle righe.
Funzioni Bash
Le funzioni consentono di organizzare lo script in blocchi logici riutilizzabili. Sono utili per evitare duplicazioni, migliorare la leggibilità e isolare parti specifiche della procedura.
#!/usr/bin/env bash
mostra_info_sistema() {
echo "Hostname: $(hostname)"
echo "Kernel: $(uname -r)"
echo "Distribuzione:"
cat /etc/os-release | grep "^PRETTY_NAME="
}
mostra_info_sistema
Una funzione può ricevere argomenti come uno script.
#!/usr/bin/env bash
saluta() {
local NOME="$1"
echo "Ciao $NOME"
}
saluta "Mario"
L’uso di local limita la visibilità della variabile alla funzione, riducendo il rischio di sovrascrivere variabili globali.
Codici di uscita e gestione degli errori
Ogni comando in GNU/Linux restituisce un codice di uscita. Il valore 0 indica generalmente successo, mentre un valore diverso da zero indica un errore o una condizione anomala.
#!/usr/bin/env bash mkdir /tmp/test-script if [ $? -eq 0 ]; then echo "Directory creata correttamente" else echo "Errore durante la creazione della directory" fi
Una forma più pulita consiste nel verificare direttamente il comando dentro la condizione.
#!/usr/bin/env bash if mkdir /tmp/test-script; then echo "Directory creata correttamente" else echo "Errore durante la creazione della directory" fi
Per rendere uno script più robusto si possono abilitare opzioni di sicurezza all’inizio del file.
#!/usr/bin/env bash set -euo pipefail echo "Script avviato"
Il significato delle opzioni è il seguente:
set -e Termina lo script se un comando restituisce errore set -u Termina lo script se viene usata una variabile non definita set -o pipefail Considera fallita una pipeline se uno dei comandi fallisce
Queste opzioni sono molto utili negli script amministrativi, ma vanno usate con consapevolezza perché possono interrompere l’esecuzione prima del previsto se non si gestiscono correttamente i casi attesi.
Redirezioni, pipe e gestione dell’output
Bash permette di redirigere output e input dei comandi. Questa caratteristica è essenziale per creare log, filtrare dati e concatenare strumenti.
comando > file.txt # Scrive l'output nel file, sovrascrivendolo comando >> file.txt # Aggiunge l'output in fondo al file comando 2> errori.log # Scrive gli errori in un file comando > out.log 2>&1 # Scrive output ed errori nello stesso file
Le pipe permettono di collegare l’output di un comando all’input di un altro.
ps aux | grep nginx cat /var/log/syslog | grep error journalctl -xe | less
In molti casi è preferibile evitare l’uso inutile di cat quando un comando può leggere direttamente il file.
grep "error" /var/log/syslog
Filtrare testo con grep, awk e sed
Gli script Bash diventano particolarmente potenti quando combinati con strumenti di elaborazione testuale. Tra i più usati ci sono grep, awk e sed.
grep "errore" applicazione.log grep -i "warning" applicazione.log grep -r "TODO" ./progetto
awk è utile per lavorare con colonne e campi.
df -h | awk '{print $1, $5, $6}'
sed permette sostituzioni e trasformazioni su flussi di testo.
sed 's/vecchio/nuovo/g' file.txt
Un esempio operativo può essere l’estrazione dei filesystem con utilizzo superiore a una determinata soglia.
#!/usr/bin/env bash
df -h | awk 'NR > 1 {print $1, $5, $6}'
Script per riconoscere la distribuzione
Su Ubuntu, Fedora e openSUSE è possibile leggere il file /etc/os-release per identificare la distribuzione in uso. Questo approccio è utile quando uno script deve scegliere automaticamente il package manager corretto.
#!/usr/bin/env bash set -euo pipefail if [ -f /etc/os-release ]; then . /etc/os-release else echo "Impossibile identificare la distribuzione" exit 1 fi echo "Distribuzione rilevata: $PRETTY_NAME" echo "ID distribuzione: $ID"
Lo script seguente mostra come selezionare il gestore pacchetti in base alla distribuzione.
#!/usr/bin/env bash
set -euo pipefail
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MANAGER="apt"
;;
fedora)
PKG_MANAGER="dnf"
;;
opensuse*|sles)
PKG_MANAGER="zypper"
;;
*)
echo "Distribuzione non supportata: $ID"
exit 1
;;
esac
echo "Gestore pacchetti rilevato: $PKG_MANAGER"
Installazione pacchetti in modo portabile
Uno script operativo può installare pacchetti usando il package manager corretto. È importante controllare i privilegi amministrativi prima di eseguire comandi che richiedono sudo o l’utente root.
#!/usr/bin/env bash
set -euo pipefail
if [ "$EUID" -ne 0 ]; then
echo "Eseguire lo script come root o tramite sudo"
exit 1
fi
. /etc/os-release
install_pacchetti() {
case "$ID" in
ubuntu|debian)
apt update
apt install -y curl wget git
;;
fedora)
dnf install -y curl wget git
;;
opensuse*|sles)
zypper refresh
zypper install -y curl wget git
;;
*)
echo "Distribuzione non supportata: $ID"
exit 1
;;
esac
}
install_pacchetti
Questo tipo di struttura è utile per creare script riutilizzabili in ambienti misti, ad esempio laboratori con macchine Ubuntu, workstation Fedora e sistemi openSUSE.
Gestione dei servizi con systemctl
Le distribuzioni moderne come Ubuntu, Fedora e openSUSE utilizzano systemd. Il comando systemctl consente di avviare, fermare, abilitare e controllare servizi di sistema.
systemctl status ssh sudo systemctl start ssh sudo systemctl stop ssh sudo systemctl restart ssh sudo systemctl enable ssh
Il nome del servizio può variare in base alla distribuzione. Ad esempio, il servizio SSH può essere indicato come ssh su Ubuntu e come sshd su Fedora e openSUSE.
#!/usr/bin/env bash
set -euo pipefail
. /etc/os-release
case "$ID" in
ubuntu|debian)
SERVIZIO_SSH="ssh"
;;
fedora|opensuse*|sles)
SERVIZIO_SSH="sshd"
;;
*)
echo "Distribuzione non supportata"
exit 1
;;
esac
systemctl status "$SERVIZIO_SSH"
Creare log personalizzati
Uno script tecnico dovrebbe produrre messaggi chiari, soprattutto se viene eseguito tramite cron, systemd timer o procedure automatiche. Una funzione di logging rende l’output più ordinato.
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="/tmp/script-operativo.log"
log_info() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $*" | tee -a "$LOG_FILE"
}
log_errore() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERRORE] $*" | tee -a "$LOG_FILE" >&2
}
log_info "Avvio procedura"
log_info "Operazione completata"
Scrivere log leggibili aiuta nella diagnosi dei problemi e consente di integrare gli script con strumenti di monitoraggio o raccolta centralizzata dei log.
Backup operativo con tar
Un caso d’uso comune per Bash è la creazione di backup compressi. Lo script seguente archivia una directory e salva il risultato in una cartella di destinazione.
#!/usr/bin/env bash set -euo pipefail SORGENTE="$HOME/documenti" DESTINAZIONE="$HOME/backup" DATA=$(date '+%Y-%m-%d_%H-%M-%S') ARCHIVIO="backup_$DATA.tar.gz" mkdir -p "$DESTINAZIONE" tar -czf "$DESTINAZIONE/$ARCHIVIO" "$SORGENTE" echo "Backup creato: $DESTINAZIONE/$ARCHIVIO"
Per evitare l’accumulo indefinito di vecchi backup, è possibile cancellare automaticamente gli archivi più datati.
#!/usr/bin/env bash set -euo pipefail DESTINAZIONE="$HOME/backup" GIORNI=30 find "$DESTINAZIONE" -type f -name "backup_*.tar.gz" -mtime +"$GIORNI" -delete
Controllo spazio disco
Uno script di controllo dello spazio disco può essere utile su server e workstation. L’esempio seguente verifica l’uso della partizione principale e produce un avviso se supera una soglia definita.
#!/usr/bin/env bash
set -euo pipefail
SOGLIA=80
UTILIZZO=$(df / | awk 'NR==2 {gsub("%","",$5); print $5}')
if [ "$UTILIZZO" -ge "$SOGLIA" ]; then
echo "ATTENZIONE: spazio disco oltre soglia: ${UTILIZZO}%"
else
echo "Spazio disco nella norma: ${UTILIZZO}%"
fi
Controllo dei processi
Bash può essere usato anche per verificare se un processo è in esecuzione. Questo approccio è utile in script di monitoraggio leggeri.
#!/usr/bin/env bash set -euo pipefail PROCESSO="nginx" if pgrep -x "$PROCESSO" > /dev/null; then echo "Il processo $PROCESSO è attivo" else echo "Il processo $PROCESSO non è attivo" fi
In alternativa, quando il software è gestito da systemd, è spesso più corretto controllare lo stato del servizio.
systemctl is-active --quiet nginx
Automazione con cron
Cron permette di eseguire script a intervalli regolari. È ancora molto usato per attività semplici come backup, pulizia temporanei e controlli periodici.
crontab -e
Esempio di esecuzione giornaliera alle 02:30:
30 2 * * * /home/utente/script/backup.sh >> /home/utente/log/backup.log 2>&1
Quando uno script viene eseguito da cron, l’ambiente è più limitato rispetto a una shell interattiva. Per questo è consigliabile usare percorsi assoluti e definire esplicitamente le variabili necessarie.
#!/usr/bin/env bash PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" echo "Esecuzione da cron: $(date)"
Automazione con systemd timer
Su sistemi moderni, i timer systemd rappresentano un’alternativa più integrata a cron. Consentono una gestione centralizzata tramite systemctl e journalctl.
Esempio di unit service:
[Unit] Description=Esecuzione script di backup [Service] Type=oneshot ExecStart=/usr/local/bin/backup.sh
Esempio di timer associato:
[Unit] Description=Timer giornaliero per backup [Timer] OnCalendar=*-*-* 02:30:00 Persistent=true [Install] WantedBy=timers.target
Dopo aver creato i file unit, si abilita il timer.
sudo systemctl daemon-reload sudo systemctl enable --now backup.timer systemctl list-timers
Parsing delle opzioni con getopts
Per script più evoluti è utile gestire opzioni da riga di comando, ad esempio -f per un file, -v per modalità verbosa o -h per mostrare l’aiuto.
#!/usr/bin/env bash
set -euo pipefail
VERBOSE=0
FILE=""
mostra_aiuto() {
echo "Uso: $0 -f <file> [-v]"
}
while getopts ":f:vh" OPZIONE; do
case "$OPZIONE" in
f)
FILE="$OPTARG"
;;
v)
VERBOSE=1
;;
h)
mostra_aiuto
exit 0
;;
:)
echo "L'opzione -$OPTARG richiede un argomento"
exit 1
;;
\?)
echo "Opzione non valida: -$OPTARG"
exit 1
;;
esac
done
if [ -z "$FILE" ]; then
mostra_aiuto
exit 1
fi
if [ "$VERBOSE" -eq 1 ]; then
echo "Modalità verbosa attiva"
fi
echo "File selezionato: $FILE"
Array in Bash
Gli array permettono di gestire liste di elementi senza ricorrere a stringhe separate manualmente. Sono utili per elenchi di pacchetti, directory, host o file.
#!/usr/bin/env bash
PACCHETTI=("curl" "wget" "git" "vim")
for PACCHETTO in "${PACCHETTI[@]}"; do
echo "Pacchetto richiesto: $PACCHETTO"
done
Gli array associativi consentono di usare chiavi testuali.
#!/usr/bin/env bash
declare -A SERVIZI
SERVIZI["ubuntu"]="ssh"
SERVIZI["fedora"]="sshd"
SERVIZI["opensuse"]="sshd"
echo "Servizio SSH su Fedora: ${SERVIZI["fedora"]}"
Trap e pulizia delle risorse temporanee
Quando uno script crea file temporanei, è opportuno rimuoverli anche in caso di interruzione. Il comando trap permette di intercettare segnali o l’uscita dello script.
#!/usr/bin/env bash
set -euo pipefail
TMP_FILE=$(mktemp)
cleanup() {
rm -f "$TMP_FILE"
echo "File temporaneo rimosso"
}
trap cleanup EXIT
echo "Dati temporanei" > "$TMP_FILE"
cat "$TMP_FILE"
Questa tecnica è particolarmente importante in script che trattano dati sensibili, generano archivi intermedi o modificano configurazioni.
Debug degli script Bash
Bash offre diverse modalità di debug. La più semplice consiste nell’eseguire lo script con l’opzione -x, che mostra i comandi espansi prima dell’esecuzione.
bash -x script.sh
La modalità di debug può essere attivata anche all’interno dello script.
#!/usr/bin/env bash set -x VARIABILE="test" echo "$VARIABILE" set +x
Per verificare la sintassi senza eseguire lo script si può usare:
bash -n script.sh
Un ulteriore strumento molto utile è ShellCheck, che analizza gli script Bash e segnala errori comuni, problemi di quoting e cattive pratiche.
# Ubuntu / Debian sudo apt install shellcheck # Fedora sudo dnf install ShellCheck # openSUSE sudo zypper install ShellCheck shellcheck script.sh
Sicurezza e buone pratiche
Gli script Bash possono modificare file, utenti, servizi e configurazioni. Per questo devono essere scritti con attenzione, soprattutto quando vengono eseguiti con privilegi elevati.
Alcune regole operative sono fondamentali: racchiudere le variabili tra doppi apici, validare sempre gli input, evitare l’esecuzione di comandi costruiti da stringhe non controllate, non usare privilegi root se non necessari e testare gli script in ambienti non critici.
#!/usr/bin/env bash set -euo pipefail DIRECTORY="$1" if [ -z "$DIRECTORY" ]; then echo "Directory non specificata" exit 1 fi if [ ! -d "$DIRECTORY" ]; then echo "La directory non esiste: $DIRECTORY" exit 1 fi echo "Directory valida: $DIRECTORY"
È inoltre consigliabile evitare costrutti pericolosi quando non strettamente necessari. Prima di usare comandi distruttivi come rm -rf, verificare sempre che la variabile di destinazione sia valorizzata e punti al percorso corretto.
#!/usr/bin/env bash set -euo pipefail TARGET="/tmp/cartella-da-rimuovere" if [ -n "$TARGET" ] && [ -d "$TARGET" ] && [[ "$TARGET" == /tmp/* ]]; then rm -rf "$TARGET" echo "Rimossa directory: $TARGET" else echo "Percorso non valido o non consentito" exit 1 fi
Esempio completo: script operativo multipiattaforma
L’esempio seguente riassume diversi concetti: rilevamento della distribuzione, controllo privilegi, funzioni, logging, gestione pacchetti e controllo di un servizio. È uno schema di partenza per script amministrativi compatibili con Ubuntu, Fedora e openSUSE.
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="/tmp/setup-base.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $*" | tee -a "$LOG_FILE"
}
errore() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERRORE] $*" | tee -a "$LOG_FILE" >&2
}
richiedi_root() {
if [ "$EUID" -ne 0 ]; then
errore "Eseguire lo script come root o tramite sudo"
exit 1
fi
}
rileva_distribuzione() {
if [ ! -f /etc/os-release ]; then
errore "File /etc/os-release non trovato"
exit 1
fi
. /etc/os-release
DISTRO_ID="$ID"
DISTRO_NAME="$PRETTY_NAME"
}
installa_pacchetti_base() {
case "$DISTRO_ID" in
ubuntu|debian)
apt update
apt install -y curl wget git vim
;;
fedora)
dnf install -y curl wget git vim
;;
opensuse*|sles)
zypper refresh
zypper install -y curl wget git vim
;;
*)
errore "Distribuzione non supportata: $DISTRO_ID"
exit 1
;;
esac
}
mostra_stato_ssh() {
case "$DISTRO_ID" in
ubuntu|debian)
SERVIZIO="ssh"
;;
fedora|opensuse*|sles)
SERVIZIO="sshd"
;;
*)
errore "Servizio SSH non determinabile"
exit 1
;;
esac
if systemctl is-active --quiet "$SERVIZIO"; then
log "Servizio $SERVIZIO attivo"
else
log "Servizio $SERVIZIO non attivo"
fi
}
main() {
richiedi_root
rileva_distribuzione
log "Distribuzione rilevata: $DISTRO_NAME"
log "Installazione pacchetti base"
installa_pacchetti_base
log "Controllo servizio SSH"
mostra_stato_ssh
log "Procedura completata"
}
main "$@"
Quando usare Bash e quando scegliere altro
Bash è ideale per automatizzare comandi di sistema, manipolare file, orchestrare strumenti già presenti nel sistema operativo e creare procedure rapide. È meno indicato quando servono strutture dati complesse, gestione avanzata degli errori, parsing articolato di formati come JSON o YAML, test applicativi estesi o logica software molto complessa.
In questi casi può essere più opportuno usare linguaggi come Python, Perl, Go o altri strumenti più adatti allo sviluppo applicativo. Una buona regola pratica è usare Bash quando lo script coordina comandi di sistema; usare un linguaggio più strutturato quando la logica interna diventa predominante rispetto ai comandi eseguiti.




