In quest'ultima lezione vedremo un esempio di applicazione dei costrutti introdotti nelle sezioni precedenti, realizzando uno script Bash completo. Lo script si occuperà di cercare ricorsivamente, in una directory, file contenenti un determinato pattern, salvandone il percorso in un file di testo e stampando a schermo il numero di file individuati.
Questo ci permetterà di mettere insieme diversi costrutti visti separatamente finora, e scoprire come Bash possa essere usato per portare a termine attività ripetitive ma semplici, che ci occuperebbero altrimenti moltissimo tempo.
Lo script che analizzeremo nel dettaglio è il seguente:
#!/bin/bash
cerca()
{
local CONT=0
for elem in `ls -d $1/*`
do
if [[ -f $elem ]]; then
grep -Fxq $2 $elem
if [[ $? == 0 ]]; then
echo $elem >> $3
((CONT++))
fi
elif [[ -d $elem ]]; then
cerca $elem $2 $3
local SUB=$?
CONT=$((SUB + CONT))
fi
done
return $CONT
}
if [[ -z "$1" || -z "$2" || -z "$3" ]]; then
echo "ERRORE: Parametri non corretti!" >&2
exit 1
elif [[ ! -d $1 ]]; then
echo "ERRORE: Directory $1 non esistente!" >&2
exit 1
else
cerca $1 $2 $3
echo "Trovati $? file in $1 contenenti il pattern $2 e salvati in $3."
fi
Si possono distinguere subito diversi comandi che abbiamo visto precedentemente: il costrutto condizionale if
, una dichiarazione di funzione, comandi aritmetici, un ciclo for
, il comando exit
, l'operatore di redirezione dell'I/O.
Come abbiamo visto nella lezione 10, una funzione viene registrata dal sistema, ma i comandi in essa contenuti non vengono eseguiti finché la funzione stessa non viene richiamata. Iniziamo perciò dal corpo dello script, che consiste in un solo comando if
:
if [[ -z "$1" || -z "$2" || -z "$3" ]]; then
echo "ERRORE: Parametri non corretti!" >&2
exit 1
elif [[ ! -d $1 ]]; then
echo "ERRORE: Directory $1 non esistente!" >&2
exit 1
else
cerca $1 $2 $3
echo "Trovati $? file in $1 contenenti il pattern $2 e salvati in $3."
fi
Questa porzione di script si occupa di controllare che i parametri forniti siano corretti e richiamare la funzione cerca
, stampando un errore nel file descriptor di errore (stderr
) in caso contrario.
La prima condizione -z
controlla che la stringa seguente non sia vuota. Viene utilizzata in questo caso per controllare che l'utente abbia indicato i tre parametri richiesti dallo script: il primo è la directory nella quale iniziare la ricerca, il secondo il pattern da individuare e l'ultimo rappresenta il nome del file in cui sarà salvato il risultato. Le tre condizioni vengono concatenate dall'operatore ||
, che permette all'intera guardia dell'if
di essere verificata nel caso in cui almeno una di esse risulti vera (in altre parole, se anche uno solo dei parametri è mancante, viene prodotto il messaggio di errore).
Il messaggio di errore viene reindirizzato verso il file descriptor stderr
mediante l'operatore >&2
. L'intero script viene perciò interrotto e il codice di uscita sarà quello di errore (1).
Se tutti i parametri sono presenti, la seconda guardia dell'if
si occupa di controllare che il primo parametro fornito sia davvero una directory esistente tramite l'operatore -d
; in caso contrario viene stampato un messaggio di errore e si esce dallo script come nel caso di mancanza di un parametro.
Infine, se i parametri inseriti sono corretti e la directory fornita esiste, lo script richiama la funzione cerca
fornendole i tre parametri, ed infine stampa il conteggio dei file trovati memorizzato nel valore di ritorno della funzione ($?
).
Veniamo adesso al corpo della funzione:
cerca()
{
local CONT=0
for elem in `ls -d $1/*`
do
if [[ -f $elem ]]; then
grep -Fxq $2 $elem
if [[ $? == 0 ]]; then
echo $elem >> $3
((CONT++))
fi
elif [[ -d $elem ]]; then
cerca $elem $2 $3
local SUB=$?
CONT=$((SUB + CONT))
fi
done
return $CONT
}
Lo scopo di questa funzione è quello di contare il numero di file contenenti il pattern nella directory fornita, e sommarlo a quello relativo alle sottodirectory in essa contenute.
Iniziamo col definire la variabile (locale, altrimenti le diverse chiamate ricorsive utilizzerebbero risultati errati) CONT
, che riporta il numero di file contenenti il pattern nella directory corrente (inizialmente 0). Il ciclo for
passa in rassegna tutti gli elementi della directory corrente (ritornati come risultato del comando ls -d $1/*
, che riporta il loro percorso completo) e ne analizza il tipo: nel caso di un file (operatore -f
nel comando if
) si controlla se quest'ultimo contiene il pattern specificato attraverso il comando grep
, il cui codice di ritorno vale 0 in caso affermativo, 1 altrimenti. Nel primo caso ($? == 0
) il percorso del file viene aggiunto al file risultato tramite l'operatore di redirezione dell'output >>
, e si aumenta il numero di file trovati nella directory corrente. Nel caso di una directory, invece (operatore -d
nel comando if
), viene effettuata la chiamata ricorsiva alla stessa funzione cerca
, passando la directory stessa come parametro di ricerca (e stesso pattern e file di output). Il risultato della chiamata ricorsiva (cioè il numero di file contenuti nella sottodirectory) viene poi aggiunto al conteggio dei file in quella corrente (SUB + CONT
). Infine, si ritorna il numero di file totale utilizzando il comando return
.
Quello appena analizzato era solo un semplice esempio di come utilizzare i costrutti forniti da Bash per rendere automatica un'attività che, altrimenti, ci avrebbe portato via molto più tempo. In questo risiede la potenza di Bash: con semplici istruzioni è possibile manipolare i componenti del sistema operativo per svolgere automaticamente attività ripetitive e tediose in maniera semplice, intuitiva e portabile su diverse piattaforme.