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.
#!/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 |
SÌ | 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. |
Se utilizzi il metodo professionale (./script.sh), segui questo ciclo:
- Crea il file:
touch script.sh - Modifica il file (includi
#!/bin/bashalla riga 1) - Rendilo eseguibile:
chmod +x script.sh - 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é.
# 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.
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:
- Il disco rigido locale
C:diventa/c/ - Il percorso precedente si traduce in:
/c/Users/Studente/Documenti - La cartella personale (Home) può essere abbreviata con la tilde:
~
Su server reali Linux, $USER contiene il nome dell'utente loggato. Su Git Bash Windows, potrebbe risultare completamente vuoto! Usa questa configurazione difensiva:
# 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.
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" }
| Sintassi | Effetto |
|---|---|
NOME="valore" | Assegna una stringa (no spazi attorno a =) |
N=42 | Assegna 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.
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)
| Tipo | Comportamento | Quando 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 |
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.
| Tipologia | Sintassi | Ambito & 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. |
# 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.
| Variabile | Contenuto | Esempio di valore |
|---|---|---|
$HOME | Home directory dell'utente | /c/Users/mario |
$USER | Nome utente corrente | mario |
$HOSTNAME | Nome del computer | PC-MARIO |
$PWD | Cartella di lavoro corrente | /c/Users/mario/lab |
$PATH | Percorsi di ricerca degli eseguibili | /usr/bin:/usr/local/bin:… |
$RANDOM | Numero intero casuale 0–32767 ad ogni lettura | 17423 |
$? | Codice di uscita dell'ultimo comando (0 = successo) | 0 |
# $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.
| Sintassi | Effetto | Esempio |
|---|---|---|
${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" |
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).
#!/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.
| Operatori | Tipo | Significato | Esempio |
|---|---|---|---|
-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 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 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.
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 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
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.
| Variabile | Contenuto | Esempio |
|---|---|---|
$0 | Nome dello script | ./script.sh |
$1, $2… | Primo argomento, secondo… | bash s.sh mario → $1 = mario |
$# | Numero totale di argomenti | 0 se nessun argomento |
$@ | Tutti gli argomenti come lista | for ARG in "$@"; do |
#!/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.
# $? 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).
# && — 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/
| Operatore | Esegue il secondo se… | Equivale a |
|---|---|---|
A && B | A ha successo (exit 0) | if A; then B; fi |
A || B | A fallisce (exit ≠ 0) | if ! A; then B; fi |
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 $().
# $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
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 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"
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.
# > 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
>
> 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.
# 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.
# 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
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.
# 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 $().
# 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
$()
$(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
| Simbolo | Significato | Esempio | Match |
|---|---|---|---|
^ | Inizio riga | ^A | Righe che iniziano con A |
$ | Fine riga | error$ | 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 newline | a.c | abc, a1c, a-c… ma non a↵c |
* | Zero o più occorrenze del precedente (illimitato) | colou*r | color, colour, colouur… |
\? | Zero o una occorrenza del precedente (al massimo una) | colou\?r | color, 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 parola | ERROR\> | ERROR ma non ERRORI |
[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.
. 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.
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.
# 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
-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:
- Owner (u): Il proprietario del file
- Group (g): Gli utenti del gruppo associato
- Others (o): Tutti gli altri utenti del sistema
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 |
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)
# 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
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.
Alcuni script leggono file di dati dalla cartella lab_bash/. Scarica lo ZIP, estrailo nella tua home directory e avvia Git Bash da lì.
- Scarica lab_bash.zip con il bottone qui sotto
- Estrai la cartella
lab_bash/inC:\Users\tuonome\(da Git Bash:~/) - Apri Git Bash:
cd ~/lab_bash - Esegui gli script con
bash nomescript.sh
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.
#!/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.
#!/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 "═══════════════════════════════════"
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.
#!/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.
#!/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
${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.
#!/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
-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.
#!/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"
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.
#!/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 "=================================="
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.
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.
#!/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.
#!/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"
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.
#!/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
#!/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
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 ==="
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 \>.
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.
#!/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"
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.
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.
#!/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
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.
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.
#!/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 "========================="
nome() { ... } — argomenti via $1, $2…; local limita la visibilità alla funzione; return N imposta il codice di uscita letto da if funzione; then.
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.
#!/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.
#!/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 "========================="
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 $RISULTATO → cut separa i due valori.
Entrambi i file devono trovarsi nella stessa cartella. Esegui con bash lab12_main.sh dalla cartella che contiene anche lab12_util.sh.