← Portale Hub Sistemi
🐚 SHELL

Bash — Guida Interattiva

Fondamenti della shell, Git Bash su Windows, 35+ comandi filtrabili, variabili globali, permessi file, laboratorio script progressivo. Corso completo per ITIS Informatica.

📖 Guida completa ⏱ ~1h 30min 🪟 Windows/Linux/macOS 35+ comandi 🎓 ITIS Informatica
#!/bin/bash # centralina.sh ID=$1 ANNO=$2 ETA=$(( 2026 - ANNO )) read -p "Operatore: " OP if [ "${#OP}" -lt 3 ]; then echo "Errore: nome troppo corto" exit 1 fi SENSORI=9 RIL=$(( SENSORI * 24 )) if [ "$RIL" -gt 200 ]; then echo "⚠ Carico elevato" fi echo "$ID · età $ETA · op $OP"

Fondamenti & Esecuzione dei File

Uno script Bash non è altro che un file di testo contenente una sequenza di comandi che la shell legge ed esegue uno dopo l'altro. Per rendere un file di testo un vero script eseguibile sul sistema operativo, è fondamentale seguire uno standard preciso.

La Struttura Anatomica dello Script

Ogni script professionale deve aprirsi con la cosiddetta Shebang (#!/bin/bash) posizionata tassativamente nella primissima riga del file. Questo indica al sistema quale interprete utilizzare per tradurre i comandi sottostanti.

Inizio di uno script standard (.sh)
#!/bin/bash
# Questo è un commento didattico: la shebang qui sopra è obbligatoria!
echo "Hello, Bash!"

Le Due Modalità di Esecuzione

Spesso si genera confusione su come lanciare uno script. In Bash hai due strade principali a tua disposizione:

Metodo Comando Richiede chmod +x? Descrizione
Standard Professionale ./mio_script.sh Usa la Shebang dichiarata nel file per eseguirlo direttamente.
Scorciatoia Diretta bash mio_script.sh NO Avvia esplicitamente Bash passandogli il file. Perfetto per sviluppo rapido.
💡 Il Ciclo Standard per Avviare uno Script

Se utilizzi il metodo professionale (./script.sh), segui questo ciclo:

  1. Crea il file: touch script.sh
  2. Modifica il file (includi #!/bin/bash alla riga 1)
  3. Rendilo eseguibile: chmod +x script.sh
  4. Avvialo: ./script.sh
💡 bash -x — Modalità debug

bash -x script.sh esegue lo script stampando ogni comando preceduto da +, prima di eseguirlo. Indispensabile quando lo script non si comporta come previsto e non si capisce perché.

Esempio di output con -x
# Lancio:  bash -x mio_script.sh

# Output che vedrai:
+ NOME=mario
+ ETA=25
+ echo 'Ciao mario'
Ciao mario
+ [ 25 -gt 18 ]
+ echo 'Sei maggiorenne'
Sei maggiorenne

Per attivare il debug solo su una sezione: set -x per iniziare, set +x per terminare.

🔬 Vai al Laboratorio → 11 script progressivi con file di supporto scaricabili

Ambiente Windows: Sviluppare in Git Bash

Poiché questo corso si appoggia alla didattica su macchine Windows, utilizzeremo l'ambiente simulato Git Bash (fornito con Git per Windows). Lavorare qui introduce dinamiche fondamentali di portabilità e compatibilità.

La Traduzione dei Percorsi (Drive Mapping)

Windows utilizza la sintassi classica delle partizioni (es: C:\Users\Studente\Documenti). Git Bash, lavorando secondo lo standard POSIX, traduce questi percorsi virtualmente:

⚠️ L'Inganno d'Ambiente: $USER vs $USERNAME

Su server reali Linux, $USER contiene il nome dell'utente loggato. Su Git Bash Windows, potrebbe risultare completamente vuoto! Usa questa configurazione difensiva:

Codice universale per intercettare l'utente
# Se $USER è vuoto, usa $USERNAME. Se entrambi vuoti, usa whoami
UTENTE_ATTUALE=${USER:-$USERNAME}
if [ -z "$UTENTE_ATTUALE" ]; then
    UTENTE_ATTUALE=$(whoami)
fi

echo "L'utente corrente è: $UTENTE_ATTUALE"

Variabili in Bash

Le variabili memorizzano valori riusabili nello script. In Bash non si dichiara il tipo: ogni variabile è una stringa, e l'aritmetica si applica con $(( )). Esistono tre ambiti: locali allo script, locali alla funzione, globali d'ambiente.

Variabili Locali

Dichiarazione senza spazi attorno al =. L'accesso avviene con $VAR; le virgolette doppie proteggono i valori con spazi al loro interno.

Dichiarazione e uso
NOME="mario"         # assegnazione — NO spazi intorno a =
ETA=25
SALUTO="Ciao $NOME"   # espansione inline della variabile

echo "$NOME"            # mario
echo "Età: $ETA anni"   # Età: 25 anni

# Dentro una funzione: local limita la visibilità alla funzione
mia_funzione() {
    local TEMP="solo qui"   # non visibile fuori dalla funzione
    echo "$TEMP"
}
SintassiEffetto
NOME="valore"Assegna una stringa (no spazi attorno a =)
N=42Assegna un numero
echo "$NOME"Legge il valore (virgolette consigliate)
local NOME="x"Visibile solo nella funzione corrente
RISULTATO=$(comando)Salva l'output di un comando in una variabile

Virgolette: ' Singole vs " Doppie

La scelta delle virgolette determina se le variabili vengono espanse o trattate come testo letterale. È una delle fonti di errore più comuni in Bash.

Confronto diretto
NOME="mario"

# Virgolette DOPPIE: le variabili vengono espanse
echo "Ciao $NOME"    # → Ciao mario
echo "$NOME"         # → mario
echo "2+2=$(( 2+2 ))" # → 2+2=4  (anche $() viene espanso)

# Virgolette SINGOLE: tutto è letterale, nessuna espansione
echo 'Ciao $NOME'    # → Ciao $NOME  (letterale)
echo '$NOME'         # → $NOME
echo '2+2=$(( 2+2 ))' # → 2+2=$(( 2+2 ))  (tutto letterale)
TipoComportamentoQuando usarle
"doppie" Variabili e $() vengono espansi Quasi sempre — proteggono anche gli spazi nei valori
'singole' Tutto letterale, nessuna espansione Quando si vuole il testo esatto, es. pattern per grep o sed
💡 Regola pratica

Usa sempre le doppie attorno alle variabili: "$VAR". Le singole servono solo quando vuoi che il testo arrivi esattamente com'è scritto, senza che Bash lo interpreti.

Variabili Globali e d'Ambiente

Se dichiari una variabile nel terminale (MIA_VAR="valore"), è locale — esiste solo nel processo corrente. Per renderla globale ed ereditabile dagli script usa export.

TipologiaSintassiAmbito & Durata
Locale MIA_VAR="valore" Valida solo nel terminale corrente. Svanisce subito.
Globale Temporanea export WORDLIST="/path" Ereditata dagli script lanciati. Svanisce alla chiusura finestra.
Globale Permanente Dentro ~/.bashrc Valida per sempre. Caricata automaticamente all'avvio.
📝 Variabile Globale Permanente in ~/.bashrc
Scrittura in ~/.bashrc
# 1. Aggiunge l'esportazione in coda a ~/.bashrc
echo 'export WORDLIST="/c/Users/utente/Documenti/words.txt"' >> ~/.bashrc

# 2. Ricarica la configurazione per applicarla subito
source ~/.bashrc

Variabili d'Ambiente Predefinite

Bash mette a disposizione variabili già valorizzate automaticamente. Alcune cambiano ad ogni accesso ($RANDOM), altre riflettono lo stato del sistema.

VariabileContenutoEsempio di valore
$HOMEHome directory dell'utente/c/Users/mario
$USERNome utente correntemario
$HOSTNAMENome del computerPC-MARIO
$PWDCartella di lavoro corrente/c/Users/mario/lab
$PATHPercorsi di ricerca degli eseguibili/usr/bin:/usr/local/bin:…
$RANDOMNumero intero casuale 0–32767 ad ogni lettura17423
$?Codice di uscita dell'ultimo comando (0 = successo)0
$RANDOM — generare numeri casuali in un intervallo
# $RANDOM restituisce un numero tra 0 e 32767
echo $RANDOM

# Numero casuale tra 1000 e 9999 (operatore modulo)
CODICE=$(( RANDOM % 9000 + 1000 ))
echo "Codice sessione: $CODICE"

# Numero casuale tra 1 e 6 (dado)
DADO=$(( RANDOM % 6 + 1 ))
echo "Hai tirato: $DADO"

Espansioni di Parametro

Sintassi avanzate con ${ } per interrogare, misurare e trasformare variabili senza comandi esterni.

SintassiEffettoEsempio
${VAR:-default} Usa default se VAR è vuota o non definita ${NOME:-"ospite"}
${#VAR} Lunghezza della stringa contenuta in VAR ${#NOME}5 se NOME="mario"
${VAR:0:n} Primi n caratteri di VAR ${NOME:0:3}"mar"
Espansioni in pratica
NOME="mario"

# Lunghezza: utile per validare input (es. nome >= 3 caratteri)
if [ ${#NOME} -lt 3 ]; then
    echo "Errore: nome troppo corto"
    exit 1
fi

# Valore di default: se l'utente non inserisce nulla
read -p "Categoria [generale]: " CAT
CAT=${CAT:-"generale"}

# Substring: prime 3 lettere come iniziali
INIZIALI=${NOME:0:3}
echo "Iniziali: $INIZIALI"   # mar

Cheat-Sheet Interattivo (35+ Comandi)

Consulta, filtra e scarica in tempo reale la tabella completa dei principali comandi Bash. Ricerca per nome, descrizione o categoria.

Costrutti di Controllo e Flusso

I costrutti logici permettono di prendere decisioni, ripetere operazioni e ricevere dati dall'esterno. Sono i mattoni di qualsiasi script non banale — il laboratorio li usa tutti; questa sezione li presenta in modo sistematico.

if / elif / else

Esegue un blocco di codice solo se una condizione è vera. Le condizioni si scrivono tra parentesi quadre [ ] seguite da ; then. Il blocco termina con fi (if al contrario).

Struttura if / elif / else
#!/bin/bash
ETA=20

if [ "$ETA" -lt 18 ]; then
    echo "Minorenne"
elif [ "$ETA" -eq 18 ]; then
    echo "Maggiorenne da oggi"
else
    echo "Adulto"
fi

Tabella Comparatori

Le condizioni all'interno di [ ] usano operatori specifici per interi, stringhe e file.

OperatoriTipoSignificatoEsempio
-lt -le -eq -ne -gt -ge Interi <   ≤   =   ≠   >   ≥ [ "$N" -gt 0 ]
=   != Stringhe uguale / diverso [ "$S" = "ok" ]
-z   -n Stringhe vuota / non vuota [ -z "$S" ]
-f   -d   -e File esiste come file / cartella / qualsiasi [ -f "dati.txt" ]

Il ciclo for

Itera su una lista di valori o su un insieme di file. Ad ogni iterazione la variabile assume il valore corrente dell'elemento.

for su lista e su file
# for su lista di valori espliciti
for GIORNO in lunedì martedì mercoledì giovedì venerdì; do
    echo "Giorno: $GIORNO"
done

# for su file tramite glob: *.txt si espande prima del ciclo
for FILE in *.txt; do
    echo "File trovato: $FILE"
done

# for su sequenza numerica: {1..5} genera 1 2 3 4 5
for I in {1..5}; do
    echo "Iterazione $I"
done

# Con step opzionale: {0..10..2} genera 0 2 4 6 8 10
for I in {0..10..2}; do
    echo "$I"
done

Il ciclo while

Continua finché una condizione rimane vera. Le due forme più comuni: lettura riga per riga da file e ciclo infinito con uscita esplicita tramite break.

while read e while true
# while con condizione esplicita su variabile di controllo
# la condizione viene rivalutata ad ogni iterazione:
# senza modificare CONTATORE dentro il ciclo si ottiene un loop infinito
CONTATORE=0
while [ "$CONTATORE" -lt 5 ]; do
    echo "Iterazione $CONTATORE"
    CONTATORE=$(( CONTATORE + 1 ))
done

# while read: legge un file riga per riga
# il simbolo < redirige il file verso l'input del ciclo
while read RIGA; do
    echo "→ $RIGA"
done < file.txt

# while true: ciclo infinito, break lo interrompe
while true; do
    read -p "Continua? (s/n): " R
    [ "$R" = "n" ] && break
done
💡 read -n 1 — singolo tasto senza Invio

read -n 1 legge esattamente un carattere e procede immediatamente, senza aspettare Invio. Combinato con while true e case crea menu interattivi reattivi. Aggiungere echo "" dopo il read per inserire il ritorno a capo mancante.

Menu a singolo tasto
while true; do
    read -n 1 -p "Stato (1=normale 2=attenzione 3=emergenza 0=esci): " TASTO
    echo ""   # read -n 1 non aggiunge newline — lo aggiungiamo noi
    case $TASTO in
        1) echo "Stato: NORMALE"    ;;
        2) echo "Stato: ATTENZIONE" ;;
        3) echo "Stato: EMERGENZA"  ;;
        0) echo "Uscita."; break    ;;
        *) echo "Tasto non valido."  ;;
    esac
done

Array

Un array contiene più valori in una sola variabile, accessibili per indice (da 0). Indispensabile quando si lavora con liste di input variabile — ad esempio temperature inserite in una sola riga.

Dichiarazione, accesso e ciclo
# Dichiarazione con valori fissi
FRUTTI=(mela pera banana)

# Accesso per indice (parte da 0)
echo "${FRUTTI[0]}"      # mela
echo "${FRUTTI[2]}"      # banana

# Tutti gli elementi
echo "${FRUTTI[@]}"      # mela pera banana

# Numero di elementi
echo "${#FRUTTI[@]}"     # 3

# Creare array da input su riga singola (parole separate da spazio)
read -p "Inserisci valori: " INPUT
ARR=($INPUT)
echo "Elementi inseriti: ${#ARR[@]}"

# Ciclo su tutti gli elementi
for ELEM in "${ARR[@]}"; do
    echo "→ $ELEM"
done
💡 Array da input: pattern fondamentale

ARR=($INPUT) — quando l'utente inserisce valori separati da spazio, Bash li suddivide automaticamente negli elementi dell'array. ${#ARR[@]} conta gli elementi: utile per verificare che il numero corrisponda a quanto atteso prima di elaborare.

Parametri Posizionali

Quando si lancia uno script con argomenti (bash script.sh arg1 arg2), Bash li assegna automaticamente a variabili speciali.

VariabileContenutoEsempio
$0Nome dello script./script.sh
$1, $2Primo argomento, secondo…bash s.sh mario$1 = mario
$#Numero totale di argomenti0 se nessun argomento
$@Tutti gli argomenti come listafor ARG in "$@"; do
Uso pratico dei parametri
#!/bin/bash
# Lancio: bash saluta.sh mario luigi

if [ $# -eq 0 ]; then
    echo "Uso: $0 <nome> [altri nomi...]"
    exit 1
fi

for NOME in "$@"; do
    echo "Ciao, $NOME!"
done

Codici di Uscita

Ogni comando termina con un codice numerico: 0 indica successo, qualsiasi altro valore un errore. $? contiene il codice dell'ultimo comando eseguito; exit N termina lo script con codice N.

exit e $?
# $? contiene il codice di uscita dell'ultimo comando
ls file.txt
echo "Codice: $?"    # 0 se il file esiste, 1 altrimenti

# Uso pratico: verifica e uscita anticipata con codice di errore
if [ ! -f "dati.txt" ]; then
    echo "Errore: dati.txt non trovato"
    exit 1
fi

echo "Elaborazione completata"
exit 0

Operatori && e ||

Permettono di concatenare comandi in base al loro esito senza scrivere un if esplicito. Usano il codice di uscita: && procede solo in caso di successo (exit 0), || solo in caso di errore (exit ≠ 0).

&& e || in pratica
# && — esegue il secondo SOLO se il primo ha successo (exit 0)
mkdir logs && echo "Cartella creata"
cd progetto && bash setup.sh     # setup.sh gira solo se cd riesce

# || — esegue il secondo SOLO se il primo fallisce (exit ≠ 0)
mkdir logs || echo "Cartella già esistente"
mkdir logs || exit 1             # esce se mkdir fallisce

# Forma compatta equivalente a: if [ -z "$NOME" ]; then exit 1; fi
[ -z "$NOME" ] && exit 1

# Combinati: prova A, se fallisce prova B
cp file.txt backup/ || cp file.txt /tmp/
OperatoreEsegue il secondo se…Equivale a
A && BA ha successo (exit 0)if A; then B; fi
A || BA fallisce (exit ≠ 0)if ! A; then B; fi
💡 if come test di comando

if comando; then — oltre alle condizioni tra [ ], if può testare direttamente il codice di uscita di qualsiasi comando: se restituisce 0 (successo) il ramo then viene eseguito. La negazione si scrive if ! comando; then. Esempio: if ! mkdir dir; then echo "Errore"; fi.

Funzioni con Parametri

Le funzioni Bash accettano argomenti esattamente come gli script: $1, $2… contengono i valori passati alla chiamata. local limita la visibilità delle variabili interne. L'output si cattura con $().

Definizione, chiamata e cattura output
# $1, $2... ricevono gli argomenti passati alla chiamata
saluta() {
    local NOME=$1
    local LINGUA=$2
    if [ "$LINGUA" = "it" ]; then
        echo "Ciao, $NOME!"
    else
        echo "Hello, $NOME!"
    fi
}
saluta "Mario" "it"    # Ciao, Mario!
saluta "Alice" "en"    # Hello, Alice!

# Cattura output con $()
conta_char() { local S=$1; echo ${#S}; }
LEN=$(conta_char "mario")
echo "Lunghezza: $LEN"   # 5
💡 Funzioni vs Script esterno

Stessa sintassi di chiamata (nome arg1 arg2), stesso accesso agli argomenti ($1, $2…). La differenza: la funzione è nello stesso file e condivide le variabili globali; lo script esterno è un processo separato e non condivide nulla.

Chiamare uno Script Esterno

Uno script può lanciare altri script come sotto-processi. È la forma più modulare di riuso del codice in Bash: il main si occupa della logica principale, i sotto-script di operazioni specifiche. L'output può essere ignorato, stampato o catturato con $().

Esecuzione e cattura output
# Esecuzione diretta — il main attende il termine
bash utilita.sh

# Con argomenti posizionali
bash utilita.sh arg1 arg2

# Con ./ (richiede permesso esecuzione: chmod +x)
./utilita.sh arg1 arg2

# Cattura l'output in una variabile — lo script non stampa a schermo
RISULTATO=$(bash utilita.sh arg1)
echo "Output ricevuto: $RISULTATO"

# Esempio reale: main chiama trova_estremi.sh e cattura min e max
ESTREMI=$(bash trova_estremi.sh -5 10 30 2 -1)
MIN=$(echo "$ESTREMI" | cut -d' ' -f1)
MAX=$(echo "$ESTREMI" | cut -d' ' -f2)
echo "Min: $MIN  Max: $MAX"
💡 Codice di uscita dello script chiamato

Dopo aver chiamato uno script, $? contiene il suo codice di uscita. Se lo script chiamato fa exit 1 in caso di errore, il main può verificare con if ! bash utilita.sh; then echo "Errore"; fi e gestire il fallimento senza interrompere l'esecuzione.

Flussi di Dati: Pipe e Ridirezione

Ogni processo Bash ha tre canali standard: stdin (input, canale 0), stdout (output normale, canale 1), stderr (errori, canale 2). Ridirezione e pipe permettono di collegare questi canali tra comandi e file.

Ridirezione Output: > e >>

> crea o sovrascrive un file con l'output del comando. >> aggiunge in coda senza cancellare il contenuto esistente.

Ridirezione output
# > crea il file o lo sovrascrive se esiste
echo "Prima riga" > log.txt

# >> aggiunge in coda senza toccare il contenuto precedente
echo "Seconda riga" >> log.txt
echo "Terza riga"  >> log.txt

# Pattern usato in funzioni.sh: intestazione con >, dati con >>
date > report.txt
df -h >> report.txt
⚠️ Attenzione a >

> sovrascrive immediatamente senza conferma. Se il file esiste, il contenuto precedente viene perso. Quando si vuole conservare il contenuto esistente, usare sempre >>.

Ridirezione Input: <

< redirige il contenuto di un file verso lo stdin di un comando. Usato con while read per leggere un file riga per riga.

Ridirezione input
# Il file viene letto riga per riga dal ciclo while
# il simbolo < in coda al done redirige verso l'intero ciclo
while read RIGA; do
    echo "→ $RIGA"
done < lista.txt

# Variante con due campi per riga (usata in stataccessi.sh)
while read NOME VALORE; do
    echo "$NOME ha valore $VALORE"
done < dati.txt

Pipe: |

Collega lo stdout di un comando allo stdin del successivo. I dati fluiscono direttamente tra processi, senza file temporanei. Si costruisce la catena passo per passo.

Pipeline: costruzione passo per passo

Il punto di partenza è quasi sempre grep. Ogni | aggiunto trasforma il risultato in modo diverso.

Da grep alla catena completa
# Passo 1 — grep da solo: trova e stampa le righe corrispondenti
grep "WARN" logs/errori.log

# Passo 2 — | wc -l: non stampa le righe, le conta
grep "WARN" logs/errori.log | wc -l

# Passo 3 — | sort: ordina i risultati alfabeticamente
grep "WARN" logs/errori.log | sort

# Passo 4 — | head -3: mostra solo i primi 3
grep "WARN" logs/errori.log | sort | head -3

# Passo 5 — | cut: estrae il terzo campo (separatore spazio)
grep "WARN" logs/errori.log | cut -d' ' -f3

# Esempio reale: utenti unici presenti nel log accessi
cat logs/accessi.log | cut -d' ' -f3 | sort | uniq
💡 Modello mentale della pipe

Ogni | prende lo stdout del comando a sinistra e lo passa come stdin al comando a destra. Il primo comando legge dal file, ogni successivo legge dall'output del precedente. Si può concatenare quanti comandi si vuole: ognuno fa una cosa sola e la fa bene.

Scartare l'Output: /dev/null

/dev/null è un file speciale che scarta tutto ciò che riceve. Utile per sopprimere errori o output non necessari in script automatizzati.

/dev/null
# 2>/dev/null scarta solo gli errori (stderr = canale 2)
ls /cartella/inesistente 2>/dev/null

# >/dev/null scarta l'output normale (stdout = canale 1)
cp file.txt backup/ >/dev/null

# &>/dev/null scarta sia stdout che stderr
comando_silenzioso &>/dev/null

Scrivere su stderr: >&2

Per convenzione i messaggi di errore vanno su stderr (canale 2), non su stdout (canale 1). Questo separa l'output utile dai messaggi diagnostici — fondamentale quando l'output dello script viene catturato con $().

>&2 in pratica
# Messaggio di errore su stderr — non viene catturato da $()
echo "Errore: file non trovato" >&2

# Confronto: stdout vs stderr
echo "Output normale"          # → va su stdout (canale 1)
echo "Messaggio di errore" >&2  # → va su stderr (canale 2)

# Perché è importante: se catturi l'output con $(), solo stdout entra
RISULTATO=$(bash script.sh)     # $RISULTATO contiene solo stdout
                                  # gli errori appaiono comunque a schermo

# Per silenziare anche stderr: redirigilo verso /dev/null
bash script.sh 2>/dev/null
💡 Subshell $()

$(comando) esegue un comando e cattura il suo stdout come stringa da assegnare a una variabile — niente va su schermo. È una forma di ridirezione "in memoria". Già usato in tutti gli script: DATA=$(date +%Y-%m-%d), MEDIA=$(awk ...), STATO=$(cat "$FILE").

Espressioni Regolari

Le espressioni regolari (regex) non sono una funzionalità di Bash — sono un linguaggio di pattern indipendente, riconosciuto da grep, sed, awk, editor di testo e linguaggi di programmazione. In Bash le usiamo principalmente con grep per filtrare righe in base a una struttura (intesa come sequenza di caratteri), non solo in base a una parola esatta.

Tabella dei simboli

SimboloSignificatoEsempioMatch
^Inizio riga^ARighe che iniziano con A
$Fine rigaerror$Righe che finiscono con error
[0-9]Una cifra decimale[0-9]Qualsiasi cifra da 0 a 9
[A-Z]Una lettera maiuscola[A-Z]Qualsiasi lettera da A a Z
[a-z]Una lettera minuscola[a-z]Qualsiasi lettera da a a z
[A-Za-z]Una lettera qualsiasi (maiuscola o minuscola)[A-Za-z]A, b, Z, m… — un solo carattere
.Un qualsiasi carattere singolo tranne il newlinea.cabc, a1c, a-c… ma non a↵c
*Zero o più occorrenze del precedente (illimitato)colou*rcolor, colour, colouur…
\?Zero o una occorrenza del precedente (al massimo una)colou\?rcolor, colour — ma non colouur
\+Una o più occorrenze del precedente (almeno una)[0-9]\+1, 42, 123…
\{n\}Esattamente n ripetizioni[0-9]\{2\}00–99 (esattamente due cifre)
\>Fine parolaERROR\>ERROR ma non ERRORI
⚠️ [A-Za-z] ≠ [A-Z][a-z]

[A-Za-z] è una classe che matcha un singolo carattere alfabetico (maiuscolo o minuscolo). [A-Z][a-z] sono due classi consecutive: matcha esattamente due caratteri, una maiuscola seguita da una minuscola — come "Ma", "Ab", "Lo". Non sono equivalenti.

💡 Il punto . non è davvero "qualsiasi"

Il punto esclude il newline (\n). In pratica con grep non fa differenza — grep lavora riga per riga e il newline non compare mai nel testo analizzato. Ma in altri contesti (sed, awk, Python, JavaScript) questa distinzione può causare bug non ovvi.

💡 * vs \? vs \+

Tre quantificatori, tre soglie diverse: * accetta da zero a infinito, \? accetta zero o uno (utile per varianti opzionali come colou\?r), \+ richiede almeno uno. In BRE (grep senza -E) il backslash è obbligatorio per \? e \+ — senza di esso sono caratteri letterali.

Esempi pratici con grep
# Righe che iniziano con A
grep "^A" log.txt

# Righe che finiscono con ERROR
grep "ERROR$" log.txt

# Orario hh:mm — due cifre, due punti, due cifre
grep "[0-9][0-9]:[0-9][0-9]" log.txt

# Stessa cosa con \{n\} — più compatto
grep "[0-9]\{2\}:[0-9]\{2\}" log.txt

# .* = qualsiasi sequenza: righe con START e poi END (qualsiasi cosa in mezzo)
grep "START.*END" log.txt

# \+ = almeno una cifra: righe che contengono un numero
grep "[0-9]\+" log.txt

# \> = fine parola: ERROR ma non ERRORI, ERRORE ecc.
grep "ERROR\>" log.txt | wc -l

# \< = inizio parola, \> = fine parola
# Parole che iniziano per A e finiscono per e (Ante, Aule, Aste…)
grep "\" log.txt
💡 Regex senza flag -E

Gli esempi usano grep in modalità standard (BRE — Basic Regular Expressions). Alcuni simboli come \+ richiedono il backslash proprio in questa modalità; con grep -E (ERE) si scrivono senza: + invece di \+, {2} invece di \{2\}. Per ora rimaniamo sulla modalità standard, che funziona identicamente su tutti i sistemi.

Sicurezza, Permessi File & Notazione Ottale

In ambiente Linux/POSIX, la sicurezza è governata da una matrice di permessi assegnata a ogni file e cartella. I permessi si dividono in tre classi di utenti:

Le Tre Operazioni Base

Ogni classe di utenti può avere diritti di Lettura (r), Scrittura (w), ed Esecuzione (x).

Operazione Simbolo File Directory
Lettura r Leggere il contenuto Listare i file dentro
Scrittura w Modificare il contenuto Creare/eliminare file dentro
Esecuzione x Eseguire come programma Accedere alla directory
📊 Notazione Ottale — Le Combinazioni Standard
  • 755 = rwx r-x r-x (proprietario esecuzione, gruppo/altri no)
  • 644 = rw- r-- r-- (solo proprietario scrive)
  • 777 = rwx rwx rwx (chiunque può fare tutto)
  • 700 = rwx --- --- (solo proprietario, accesso totale)
Comandi chmod comuni
# Rendere uno script eseguibile
chmod +x script.sh
chmod 755 script.sh

# Permettere a gruppo e altri di leggere/eseguire
chmod 755 file.txt

# Accesso solo al proprietario
chmod 700 file.txt

# Visualizzare permessi dettagliati
ls -l

Laboratorio Script Progressivo

↑ torna alla guida

Tredici script con difficoltà crescente, organizzati per tema. Ogni script introduce nuovi costrutti Bash. Gli script di File & Dati che leggono file esterni e quelli di Strutture Avanzate richiedono i file inclusi nello ZIP.

📦 File di supporto — ServerLab

Alcuni script leggono file di dati dalla cartella lab_bash/. Scarica lo ZIP, estrailo nella tua home directory e avvia Git Bash da lì.

  1. Scarica lab_bash.zip con il bottone qui sotto
  2. Estrai la cartella lab_bash/ in C:\Users\tuonome\  (da Git Bash: ~/)
  3. Apri Git Bash: cd ~/lab_bash
  4. Esegui gli script con bash nomescript.sh
📥 Scarica lab_bash.zip Necessario per gli script 6, 9–12 e 13
benvenuto.sh — Variabili e Echo Fondamenti

Script 1: benvenuto.sh — Hello World Personalizzato

Il primo script: dichiarare variabili locali e stampare valori a schermo. Attenzione alla sintassi: in Bash non si mettono spazi intorno al segno = nelle assegnazioni.

Variante Base

Dichiara due variabili, intercetta l'utente di sistema e stampa un messaggio di benvenuto.

benvenuto.sh — variante base
#!/bin/bash

# Dichiarazione variabili locali (ATTENZIONE: nessun spazio intorno all'uguale '=')
NOME="Sviluppatore"
CORSO="Bash Base"

# Intercettiamo l'utente in modo robusto per Windows/Git Bash:
# ${USER:-$USERNAME} significa: usa $USER, se vuoto usa $USERNAME
UTENTE_CORRENTE=${USER:-$USERNAME}

# Stampe dell'output sul terminale
echo "Benvenuto, $NOME!"
echo "Utente di sistema corrente: $UTENTE_CORRENTE"
echo "Stai seguendo il corso: $CORSO"

Variante Intermedia

Aggiunge il costrutto if per gestire il caso in cui sia $USER che $USERNAME siano vuoti (possibile su alcuni ambienti Windows), e usa $(date) per stampare data e ora correnti.

benvenuto.sh — variante intermedia
#!/bin/bash

# Dichiarazione variabili locali
NOME="Sviluppatore"
CORSO="Bash Base"

# Intercettiamo l'utente: primo tentativo con ${USER:-$USERNAME}
UTENTE_CORRENTE=${USER:-$USERNAME}

# if [ -z "..." ] verifica se la stringa è vuota (zero-length)
# Se entrambe le variabili d'ambiente sono vuote, usiamo il comando whoami
if [ -z "$UTENTE_CORRENTE" ]; then
    UTENTE_CORRENTE=$(whoami)
fi

# $(date '+formato') esegue il comando date e ne cattura l'output
# %d = giorno, %m = mese, %Y = anno, %H = ore, %M = minuti, %S = secondi
DATA_ORA=$(date '+%d/%m/%Y %H:%M:%S')

# Stampe dell'output sul terminale
echo "═══════════════════════════════════"
echo "Benvenuto, $NOME!"
echo "Utente di sistema: $UTENTE_CORRENTE"
echo "Corso: $CORSO"
echo "Data e ora: $DATA_ORA"
echo "═══════════════════════════════════"
💡 Come usarlo

Copia il codice, salvalo in benvenuto.sh, esegui chmod +x benvenuto.sh, quindi ./benvenuto.sh

progetto.sh — Read e Mkdir Fondamenti

Script 2: progetto.sh — Creazione Cartelle Progetto

Chiede all'utente il nome di un progetto via read e crea automaticamente la struttura di cartelle. Usa mkdir -p per creare cartelle annidate senza errori se già esistono.

progetto.sh
#!/bin/bash

# Intercetta l'utente corrente (compatibile Windows/Git Bash)
UTENTE=${USER:-$USERNAME}
echo "Benvenuto utente $UTENTE!!"

# Richiesta di input testuale: read legge ciò che l'utente digita
echo "Inserisci il nome del progetto da creare:"
read NOME_PROGETTO

echo "Creazione cartella in corso..."

# mkdir -p: crea la cartella genitore "Progetti" se non esiste,
# e non restituisce errore se la sottocartella esiste già
mkdir -p "Progetti/$NOME_PROGETTO"

echo "Cartella '$NOME_PROGETTO' creata con successo!"

# ls -ld mostra i dettagli di una cartella specifica (non il suo contenuto)
ls -ld "Progetti/$NOME_PROGETTO"
progetti.sh — Default e While True Fondamenti

Script 3: progetti.sh — Cartelle con Input Ripetuto

Variante di progetto.sh: la cartella principale ha un nome di default configurabile e le sottocartelle si aggiungono in un ciclo finché l'utente non preme Invio senza scrivere nulla. Introduce il valore di default su input vuoto e il ciclo while true con uscita esplicita tramite break.

progetti.sh
#!/bin/bash
# progetti.sh — Creazione struttura cartelle multi-progetto
# Variante di progetto.sh: cartella principale configurabile
# e aggiunta iterativa di sottocartelle tramite ciclo while

# read -p legge l'input dell'utente e lo salva in PRINCIPALE
# Se l'utente preme Invio senza scrivere nulla, PRINCIPALE vale ""
read -p "Nome cartella principale [progetti]: " PRINCIPALE

# Sintassi ${VAR:-valore_di_default}:
# - se $PRINCIPALE non è vuota, usa il valore inserito dall'utente
# - se $PRINCIPALE è vuota (Invio senza scrivere), usa "progetti"
# È il modo idiomatico in Bash per gestire input opzionali
PRINCIPALE=${PRINCIPALE:-"progetti"}

echo "Cartella principale: $PRINCIPALE"
mkdir -p "$PRINCIPALE"

echo ""
echo "Inserisci le sottocartelle una alla volta."
echo "Premi Invio senza scrivere nulla per terminare."
echo ""

# while true crea un ciclo potenzialmente infinito:
# non c'è una condizione di uscita nell'intestazione del ciclo,
# ma all'interno possiamo uscire in qualsiasi momento con break
while true; do

    read -p "  Sottocartella (Invio per finire): " NOME

    # [ -z "$NOME" ] è vero se la stringa è vuota (zero-length)
    # Quando l'utente preme Invio senza scrivere, NOME vale ""
    # break interrompe immediatamente il ciclo while e passa
    # all'istruzione successiva (echo "Struttura finale:")
    if [ -z "$NOME" ]; then
        break
    fi

    mkdir -p "$PRINCIPALE/$NOME"
    echo "  ✓ Creata: $PRINCIPALE/$NOME"

done

echo ""
echo "Struttura finale:"
# find -type d elenca solo le cartelle (esclude i file), sort le ordina
find "$PRINCIPALE" -type d | sort
💡 Tre costrutti nuovi

${VAR:-default} imposta un valore di fallback se la variabile è vuota. while true avvia un ciclo senza condizione di uscita: continua finché non interviene break. find -type d elenca ricorsivamente solo le cartelle.

classifica.sh — Parametri e Condizionali Fondamenti

Script 3: classifica.sh — Parametri e Costrutti Condizionali

Riceve un numero come argomento da riga di comando e lo classifica. Introduce i parametri posizionali ($1, $#, $0) e i comparatori matematici di Bash.

classifica.sh
#!/bin/bash

# $# conta quanti argomenti l'utente ha inserito a riga di comando
# Se non ne ha inseriti, mostriamo l'uso corretto ed usciamo
if [ $# -eq 0 ]; then
    echo "Errore sintattico. Uso corretto: $0 <numero>"
    exit 1
fi

# $1 è il primo parametro inviato (es: ./classifica.sh 5 → $1 vale 5)
numero=$1

# Comparatori per costrutti if-else matematici:
# -lt (less than, <) | -eq (equal, ==) | -le (less or equal, <=)
if [ "$numero" -lt 0 ]; then
    echo "negativo"
elif [ "$numero" -eq 0 ]; then
    echo "zero"
elif [ "$numero" -le 9 ]; then
    echo "piccolo"
else
    echo "grande"
fi
📊 Riepilogo comparatori Bash

-lt (less than), -le (less or equal), -eq (equal), -ne (not equal), -gt (greater than), -ge (greater or equal).

media.sh — While Read e Aritmetica File & Dati

Script 4: media.sh — Calcolo Media Aritmetica

Legge voti da un file di testo (uno per riga), calcola la media intera con Bash e poi quella decimale precisa usando awk. Se il file non esiste, ne crea uno di prova.

media.sh
#!/bin/bash

# Verifica se il file dei dati esiste. Se manca, lo creiamo di esempio
if [ ! -f voti.txt ]; then
    echo "File 'voti.txt' non trovato. Inizializzo file di prova..."
    echo -e "18\n24\n30\n26\n22" > voti.txt
fi

somma=0
conta=0

# Il ciclo while read legge il file passato in fondo col simbolo '<'
while read voto; do
    # Ignoriamo eventuali righe vuote nel documento
    [ -z "$voto" ] && continue

    somma=$((somma + voto))
    conta=$((conta + 1))
done < voti.txt

# Divisione intera standard di Bash (non supporta decimali)
media=$((somma / conta))

echo "Somma dei voti analizzati: $somma"
echo "Numero di righe elaborate: $conta"
echo "Media (arrotondata all'intero per limite Bash): $media"

# RISOLUZIONE DEL LIMITE: integrazione con AWK per calcolo decimale preciso
media_precisa=$(awk '{somma+=$1; conta++} END {print somma/conta}' voti.txt)
echo "Media reale (con cifre decimali tramite AWK): $media_precisa"
⚠️ Limite di Bash

Bash supporta solo aritmetica intera. Per calcoli con decimali è necessario integrare strumenti esterni come awk o bc.

stataccessi.sh — Min, Max e Media File & Dati

Script 6: stataccessi.sh — Statistiche di Accesso

Legge un file con utente e ore di accesso e restituisce il massimo, il minimo e la media. Introduce read a due campi contemporanei e il pattern di inizializzazione min/max con i comparatori -gt e -lt già visti in classifica.sh.

stataccessi.sh
#!/bin/bash
# stataccessi.sh — Statistiche accessi al sistema

# MAX_ORE parte da 0: qualsiasi valore reale sarà maggiore
# MIN_ORE parte da 99999: qualsiasi valore reale sarà minore
# È il modo standard per inizializzare min/max prima di leggere i dati
MAX_ORE=0
MIN_ORE=99999
MAX_UTENTE=""
MIN_UTENTE=""
TOTALE=0
CONTATORE=0

# while read NOME ORE legge due campi per riga separati da spazio
while read NOME ORE; do

    [ -z "$NOME" ] && continue

    TOTALE=$((TOTALE + ORE))
    CONTATORE=$((CONTATORE + 1))

    # Aggiorna il massimo se le ore correnti superano il massimo trovato finora
    if [ "$ORE" -gt "$MAX_ORE" ]; then
        MAX_ORE=$ORE
        MAX_UTENTE=$NOME
    fi

    # Aggiorna il minimo se le ore correnti sono inferiori al minimo trovato finora
    if [ "$ORE" -lt "$MIN_ORE" ]; then
        MIN_ORE=$ORE
        MIN_UTENTE=$NOME
    fi

done < dati/accessi.txt

# Divisione intera: media approssimata all'ora intera (limite di Bash)
MEDIA=$((TOTALE / CONTATORE))

echo "=== ANALISI ACCESSI AL SISTEMA ==="
echo ""
echo "  Accesso maggiore: $MAX_UTENTE ($MAX_ORE ore)"
echo "  Accesso minore:   $MIN_UTENTE ($MIN_ORE ore)"
echo "  Media totale:     $MEDIA ore"
echo ""
echo "=================================="
💡 Read a campi multipli e pattern min/max

while read A B; do ... done < file — Bash assegna i campi separati da spazio a variabili distinte in una sola operazione. L'inizializzazione di MAX=0 e MIN=99999 è il pattern standard per trovare massimo e minimo senza conoscere i dati in anticipo.

⚠️ Prerequisito

Esegui da ~/lab_bash/. Il file dati/accessi.txt deve essere presente (incluso nello ZIP).

cercaparola.sh — Grep e Variabili Globali File & Dati

Script 5: cercaparola.sh — Ricerca e Conteggio

Cerca una parola in un file di testo e conta le occorrenze. Due varianti: una base con input interattivo, una avanzata che sfrutta la variabile globale $WORDLIST.

Variante Base

Chiede all'utente parola e file da cercare tramite read -p. Usa grep -o per contare le singole occorrenze.

cercaparola.sh — variante base
#!/bin/bash

# Chiediamo all'utente cosa cercare e dove
read -p "Inserisci la parola da cercare: " PAROLA
read -p "Inserisci il file dove cercare: " FILE

# Verifica che il file esista prima di procedere
if [ ! -f "$FILE" ]; then
    echo "Errore: file non trovato!"
    exit 1
fi

# grep -o stampa solo le corrispondenze (una per riga), wc -l le conta
CONTEGGIO=$(grep -o "$PAROLA" "$FILE" | wc -l)
echo "La parola '$PAROLA' appare $CONTEGGIO volte in $FILE"

Variante Avanzata (con $WORDLIST)

Usa la variabile globale $WORDLIST (definita con export nella sezione 4) come dizionario sorgente. Se la variabile non è stata impostata, crea un file locale di fallback. Accetta la parola sia da argomento che da input interattivo.

cercaparola.sh — variante avanzata
#!/bin/bash

# 1. Sicurezza preventiva: la variabile globale $WORDLIST esiste?
if [ -z "$WORDLIST" ]; then
    echo "ATTENZIONE: La variabile globale \$WORDLIST non risulta valorizzata!"
    echo "Abilitazione fallback locale per impedire l'arresto dello script..."
    WORDLIST="./words.txt"

    # Se non esiste neanche il file locale, ne generiamo uno al volo
    if [ ! -f "$WORDLIST" ]; then
        echo -e "casa\nalbero\ntavolo\nbash\ncorso\nsviluppatore\nsistema" > "$WORDLIST"
    fi
fi

echo "Dizionario sorgente caricato da: $WORDLIST"

# 2. Controllo degli argomenti del terminale
if [ $# -eq 0 ]; then
    # Se l'utente non ha passato parametri, li chiediamo con 'read'
    read -p "Inserisci la parola da cercare: " parola
else
    # Altrimenti prendiamo il primo argomento passato
    parola=$1
fi

# 3. Ricerca effettiva (-i = case-insensitive)
echo "Corrispondenze trovate:"
grep -i "$parola" "$WORDLIST"

# 4. Conteggio complessivo delle righe trovate tramite flag '-c'
trovate=$(grep -i -c "$parola" "$WORDLIST")
echo "Totale righe corrispondenti trovate: $trovate"
💡 Prerequisito per la variante avanzata

Per usare $WORDLIST senza fallback, impostala prima con: export WORDLIST="/percorso/al/dizionario.txt" (vedi sezione 4 — Variabili Globali).

backup_progetti.sh — Date e Copia Ricorsiva File & Dati

Script 6: backup_progetti.sh — Archiviazione con Timestamp

Crea una copia di backup della cartella Progetti aggiungendo la data e ora correnti al nome dell'archivio. Combina date, if, mkdir -p e cp -r.

backup_progetti.sh
#!/bin/bash

SORGENTE="Progetti"
DESTINAZIONE="Backup"
# Definiamo la stringa temporale con anno, mese, giorno, ore e minuti
DATA=$(date +%Y-%m-%d_%H-%M)

# Verifichiamo l'esistenza della sorgente con -d (directory exists)
if [ ! -d "$SORGENTE" ]; then
    echo "Errore critico: La cartella sorgente '$SORGENTE' non esiste su disco."
    exit 1
fi

# Genera la cartella di backup se mancante
mkdir -p "$DESTINAZIONE"

echo "Avvio dell'archiviazione dati..."

# Copia ricorsiva (-r) della sorgente, rinominando con la data corrente
cp -r "$SORGENTE" "$DESTINAZIONE/backup_progetti_$DATA"

echo "Operazione conclusa con successo. Archivio registrato in: $DESTINAZIONE/backup_progetti_$DATA"
grep_regex.sh — Ricerca con Regex File & Dati

Script 13: grep_regex.sh — Pattern di Ricerca sui Log

Applica le espressioni regolari ai file di log del ServerLab con grep. Ogni comando dimostra un simbolo regex diverso sugli stessi dati — confronta gli output per capire cosa filtra ogni pattern.

Parte 1 — logs/errori.log

grep_regex.sh — errori.log
#!/bin/bash
# grep_regex.sh — ricerca con regex sui file di log
# Prerequisito: esegui da ~/lab_bash/

echo "=== RICERCHE SU errori.log ==="

# 1. Ricerca semplice: include anche ERRORI_parser e simili
echo "--- 1. grep ERROR (include varianti) ---"
grep "ERROR" logs/errori.log
echo ""

# 2. \> = fine parola: ERROR ma NON ERRORI, ERRORI_parser
#    Confronta con il risultato 1 per vedere la differenza
echo "--- 2. grep ERROR\> (solo parola ERROR) ---"
grep "ERROR\>" logs/errori.log
echo ""

# 3. ^ = inizio riga: solo le righe del 16 luglio
echo "--- 3. ^ inizio riga: log del 2026-07-16 ---"
grep "^2026-07-16" logs/errori.log
echo ""

# 4. [0-9]\{2\}:[0-9]\{2\} = orario hh:mm
echo "--- 4. orario hh:mm ---"
grep "[0-9]\{2\}:[0-9]\{2\}" logs/errori.log | head -5
echo ""

# 5. Conta solo le righe con ERROR (parola esatta)
echo "--- 5. Conteggio righe ERROR ---"
N=$(grep "ERROR\>" logs/errori.log | wc -l)
echo "Trovate: $N righe con ERROR"

Parte 2 — logs/accessi.log

grep_regex.sh — accessi.log
echo "=== RICERCHE SU accessi.log ==="

# 6. $ = fine riga: solo accessi di tipo LOGIN
echo "--- 6. $ fine riga: solo LOGIN ---"
grep "LOGIN$" logs/accessi.log
echo ""

# 7. Ricerca per utente
echo "--- 7. Tutte le righe di mario ---"
grep "mario" logs/accessi.log
echo ""

# 8. Pipeline: utenti unici con LOGIN
#    grep filtra → cut estrae il nome → sort ordina → uniq toglie duplicati
echo "--- 8. Utenti unici con LOGIN ---"
grep "LOGIN$" logs/accessi.log | cut -d' ' -f3 | sort | uniq

echo ""
echo "=== Fine ricerche ==="
💡 Confronta i risultati 1 e 2

Il comando 1 restituisce anche le righe con ERRORI_parser. Il comando 2 con ERROR\> filtra solo la parola esatta. Confrontare i due output è il modo più diretto per capire cosa fa \>.

⚠️ Prerequisito

Esegui da ~/lab_bash/. Richiede lo ZIP aggiornato (logs/accessi.log 24 righe, logs/errori.log 17 righe).

contatore.sh — Ciclo For e Basename Strutture Avanzate

Script 9: contatore.sh — Iterazione con ciclo for

Legge tutti i file .status nella cartella servizi/ e verifica se ogni servizio è attivo. Introduce il ciclo for su file tramite glob, il comando basename e i contatori numerici.

contatore.sh
#!/bin/bash
# contatore.sh — Controllo stato servizi ServerLab

SERVIZI_DIR="servizi"

echo "═══════════════════════════════════"
echo "  CONTROLLO SERVIZI — ServerLab"
echo "  Data: $(date '+%d/%m/%Y %H:%M:%S')"
echo "═══════════════════════════════════"

ATTIVI=0
INATTIVI=0

# Il ciclo for itera su ogni file .status trovato nella cartella
# Il glob *.status viene espanso dalla shell prima che il ciclo inizi
for FILE in "$SERVIZI_DIR"/*.status; do

    # basename rimuove percorso ed estensione: servizi/web.status → web
    SERVIZIO=$(basename "$FILE" .status)

    # cat legge l'unica riga del file (ATTIVO oppure INATTIVO)
    STATO=$(cat "$FILE")

    if [ "$STATO" = "ATTIVO" ]; then
        echo "  [ OK ] $SERVIZIO → $STATO"
        ATTIVI=$((ATTIVI + 1))
    else
        echo "  [WARN] $SERVIZIO → $STATO"
        INATTIVI=$((INATTIVI + 1))
    fi

done

echo "═══════════════════════════════════"
echo "  Attivi: $ATTIVI  |  Inattivi: $INATTIVI"
💡 Struttura del ciclo for su file

for VAR in percorso/*.ext; do ... done — il glob viene espanso dalla shell prima che il ciclo inizi. basename "$FILE" .ext rimuove percorso ed estensione in una sola operazione.

⚠️ Prerequisito

Esegui da ~/lab_bash/. La cartella servizi/ con i tre file .status deve essere presente (inclusa nello ZIP).

menu.sh — Case e While True Strutture Avanzate

Script 10: menu.sh — Pannello di Controllo Interattivo

Un pannello a menu numerato: l'utente sceglie un'azione e lo script la esegue, ripetendo il ciclo finché non decide di uscire. Introduce case, il loop infinito while true e il comando break.

menu.sh
#!/bin/bash
# menu.sh — Pannello di controllo interattivo ServerLab

while true; do
    echo ""
    echo "╔══════════════════════════════╗"
    echo "║   PANNELLO SERVERLAB         ║"
    echo "╠══════════════════════════════╣"
    echo "║  1) Ultimi accessi al server ║"
    echo "║  2) Spazio disco disponibile ║"
    echo "║  3) Elenco utenti registrati ║"
    echo "║  4) Ultimi errori di sistema ║"
    echo "║  0) Esci dal pannello        ║"
    echo "╚══════════════════════════════╝"
    read -p "  Scelta: " SCELTA
    case $SCELTA in
        1) echo "--- Ultimi 5 accessi ---"; tail -n 5 logs/accessi.log ;;
        2) echo "--- Spazio disco ---"; df -h . ;;
        3) echo "--- Utenti registrati ---"
           for F in utenti/*.txt; do echo "  · $(basename "$F" .txt)"; done ;;
        4) echo "--- Ultimi 3 errori ---"; tail -n 3 logs/errori.log ;;
        0) echo "Uscita dal pannello."; break ;;
        *) echo "Scelta non valida." ;;
    esac
done
💡 Struttura case in Bash

case $VAR in pattern) ... ;; *) ... ;; esac — il ramo *) è il default. Ogni ramo termina con ;;. Più leggibile di una catena elif quando i casi sono numerosi.

⚠️ Prerequisito

Esegui da ~/lab_bash/. Le cartelle logs/ e utenti/ devono essere presenti (incluse nello ZIP).

funzioni.sh — Funzioni e Return Strutture Avanzate

Script 11: funzioni.sh — Toolkit Riutilizzabile

Introduce le funzioni Bash: blocchi di codice con nome, richiamabili più volte con argomenti diversi. Lo script definisce tre funzioni e le chiama in sequenza, simulando un ciclo di manutenzione del server.

funzioni.sh
#!/bin/bash
# funzioni.sh — Toolkit di manutenzione ServerLab
# Le funzioni si definiscono PRIMA di essere chiamate

# ─── FUNZIONE 1: scrivi una riga nel log di sistema ───────────────
# Argomenti: $1 = tipo (INFO/WARN), $2 = messaggio
scrivi_log() {
    local TIPO=$1
    local TESTO=$2
    # local limita la variabile allo scope della funzione
    local TS=$(date '+%Y-%m-%d %H:%M:%S')
    echo "$TS [$TIPO] $TESTO" >> logs/sistema.log
    echo "  → Log: [$TIPO] $TESTO"
}

# ─── FUNZIONE 2: verifica esistenza utente ────────────────────────
# Argomenti: $1 = nome utente
# Return: 0 se trovato (successo), 1 se non trovato (errore)
utente_esiste() {
    local NOME=$1
    if [ -f "utenti/$NOME.txt" ]; then
        return 0   # trovato: la funzione "ha successo"
    else
        return 1   # non trovato: la funzione "fallisce"
    fi
}

# ─── FUNZIONE 3: genera report spazio disco ───────────────────────
# Nessun argomento: usa la data odierna come suffisso del file
genera_report() {
    local OUTFILE="report/disco_$(date +%Y-%m-%d).txt"
    echo "Report disco — $(date '+%d/%m/%Y %H:%M')" > "$OUTFILE"
    echo "──────────────────────────────" >> "$OUTFILE"
    df -h . >> "$OUTFILE"
    echo "  → Report salvato: $OUTFILE"
}

# ─── ESECUZIONE PRINCIPALE ────────────────────────────────────────

echo "=== TOOLKIT SERVERLAB ==="
scrivi_log "INFO" "Avvio toolkit"

echo ""
echo "Verifica utenti:"
for UTENTE in mario anna guest ospite; do
    # if chiama la funzione e usa il suo valore di return (0 = successo)
    if utente_esiste "$UTENTE"; then
        echo "  [ OK ] $UTENTE presente"
        scrivi_log "INFO" "Utente $UTENTE verificato"
    else
        echo "  [WARN] $UTENTE NON trovato"
        scrivi_log "WARN" "Utente $UTENTE assente"
    fi
done

echo ""
genera_report
scrivi_log "INFO" "Toolkit completato"
echo "========================="
💡 Funzioni in Bash

nome() { ... } — argomenti via $1, $2…; local limita la visibilità alla funzione; return N imposta il codice di uscita letto da if funzione; then.

⚠️ Prerequisito

Esegui da ~/lab_bash/. Le cartelle utenti/, logs/ e report/ devono essere presenti (incluse nello ZIP).

lab12_main.sh — Chiamata a Script Esterno Strutture Avanzate

Script 12: main + util — Comunicazione tra Script

Due script che lavorano insieme: lab12_main.sh legge i dati dall'utente e chiama lab12_util.sh passandogli i valori come argomenti. Lo script di utilità calcola minimo e massimo e li stampa su una riga; il main cattura l'output con $() e lo elabora.

Passo 1 — lab12_util.sh (lo script chiamato)

Riceve valori esclusivamente tramite argomenti ($1, $2…), non usa read. Stampa il risultato su una riga con echo — sarà catturato dal main.

lab12_util.sh
#!/bin/bash
# lab12_util.sh — trova minimo e massimo di una lista di numeri
# Riceve valori SOLO come argomenti: non usa read
# Output: "MIN MAX" su una riga, catturato dal main con $()

if [ $# -eq 0 ]; then
    echo "Errore: nessun valore ricevuto" >&2
    exit 1
fi

# Inizializza min e max con il primo valore
MIN=$1
MAX=$1

# Itera su tutti gli argomenti con "$@"
for VALORE in "$@"; do
    if [ "$VALORE" -lt "$MIN" ]; then
        MIN=$VALORE
    fi
    if [ "$VALORE" -gt "$MAX" ]; then
        MAX=$VALORE
    fi
done

# echo stampa "MIN MAX" — il main lo leggerà con cut
echo "$MIN $MAX"

Passo 2 — lab12_main.sh (lo script principale)

Legge l'input, costruisce l'array, chiama lo script esterno con $(bash lab12_util.sh "${ARR[@]}") e analizza la risposta.

lab12_main.sh
#!/bin/bash
# lab12_main.sh — legge valori e chiama lab12_util.sh per l'analisi

echo "=== ANALISI VALORI ==="
read -p "Inserisci numeri separati da spazio: " INPUT

# Converte l'input in un array (ogni parola diventa un elemento)
ARR=($INPUT)

if [ ${#ARR[@]}" -eq 0 ]; then
    echo "Errore: nessun valore inserito"
    exit 1
fi

echo "Valori inseriti: ${#ARR[@]}"

# Chiama lo script esterno passando tutti gli elementi dell'array
# "${ ARR[@]}" espande l'array come lista di argomenti separati
# $() cattura ciò che lab12_util.sh stampa con echo
RISULTATO=$(bash lab12_util.sh "${ARR[@]}")

# L'output è "MIN MAX" — cut estrae il primo e il secondo campo
MIN=$(echo "$RISULTATO" | cut -d' ' -f1)
MAX=$(echo "$RISULTATO" | cut -d' ' -f2)

echo ""
echo "Minimo: $MIN"
echo "Massimo: $MAX"
echo "========================="
🔗 Flusso di esecuzione

main legge input → crea array → chiama bash lab12_util.sh 5 -3 12 8 → util riceve quei 4 argomenti in $@, calcola min/max, stampa "-3 12" → main cattura "-3 12" in $RISULTATOcut separa i due valori.

⚠️ Prerequisito

Entrambi i file devono trovarsi nella stessa cartella. Esegui con bash lab12_main.sh dalla cartella che contiene anche lab12_util.sh.