Fino ad ora abbiamo visto caratteristiche fondamentali della Bash che consentono di definire variabili ed eseguire sequenze di comandi. Per costruire strutture algoritmiche più complesse è necessario utilizzare delle istruzioni che permettano di implementare strutture di controllo condizionali o iterative, come avviene in tutti i linguaggi di programmazione strutturata.
Una struttura di controllo condizionale consente di valutare un'espressione logica e, sulla base del suo valore (vero o falso), eseguire determinate istruzioni piuttosto che altre. La forma più elementare di questa struttura di controllo è la seguente:
1. se condizione allora
2. istruzioni
3. fine-condizione
Nel linguaggio Bash questa struttura viene implementata dalle istruzioni “if” e “fi” secondo la seguente sintassi:
1 if lista di comandi; then
2 lista di comandi;
3 fi
L'istruzione “fi” chiude la struttura di controllo condizionale, aperta dall'istruzione “if”: in altri termini ad ogni istruzione “if” deve corrispondere, nelle righe successive, un'istruzione “fi”. La lista dei comandi che segue l'istruzione “then” viene eseguita se la lista dei comandi che segue l'istruzione “if” restituisce il valore 0 (vero).
Ogni comando eseguito dalla shell restituisce un valore numerico che la shell interpreta come “vero” se tale valore è zero, o “falso” se il comando restituisce un qualsiasi codice diverso da zero. Pertanto ciascun comando, anche composto, può essere utilizzato come condizione in un'istruzione “if”. Nell'esempio seguente si utilizza il return code del comando “grep” come condizione per verificare se l'utente “root” è definito sul sistema.
1 #!/bin/bash
2 if grep -q root /etc/passwd; then
3 echo "L'utente root e' definito!"
4 fi
Utilizzando invece la sintassi “[[...]]”, illustrata nel Capitolo 2, è possibile eseguire confronti fra variabili e sullo stato dei file. Le espressioni che esprimono una condizione logica che deve essere valutata interpretando specifici operatori, devono essere delimitate da coppie di doppie parentesi quadrate “[[...]]” per poter essere elaborate dalla Bash. Ad esempio il seguente script visualizza il numero di utenti definiti sul sistema se il file “/etc/passwd” esiste.
1 #!/bin/bash
2 if [[ -e /etc/passwd ]]; then
3 wc -l /etc/passwd
4 fi
Una versione più completa della struttura di controllo condizionale consente di gestire due liste di istruzioni alternative: la prima viene eseguita nel caso in cui il valore della condizione sia “vero”, la seconda viene eseguita se invece il valore della condizione è “falso”:
1. se condizione allora
2. istruzioni
3. altrimenti
4. istruzioni
5. fine-condizione
Questa struttura algoritmica è implementata dalle istruzioni “if ... then ... else ... fi”:
1 if lista di comandi; then
2 lista di comandi
3 else
4 lista di comandi
5 fi
Lo script presentato nell'esempio precedente può essere quindi modificato aggiungendo la clausola “else” come segue:
1 #!/bin/bash
2 if [[ -e /etc/passwd ]]; then
3 wc -l /etc/passwd
4 else
5 echo 'Il file /etc/passwd non esiste'
6 fi
In Figura sono rappresentati i diagrammi di flusso corrispondenti alla struttura di controllo “if ... then ... fi” e alla struttura “if ... then ... else ... fi”.
Diagrammi di flusso delle strutture di controllo condizionali: (a) “if ... then ... fi” e (b) “if ... then ... else ... fi”.
È possibile codificare una struttura condizionale più articolata, con la verifica di condizioni “a cascata”, secondo il seguente schema:
1. se condizione1 allora
2. istruzioni1
3. altrimenti se condizione2 allora
4. ...
5. altrimenti se condizionen allora
6. istruzionin
7. altrimenti
8. istruzionin+1
9. fine-condizione
Se risulta verificata la condizione i-esima, allora viene eseguito il blocco i-esimo di istruzioni. Se nessuna delle condizioni risulta verificata, viene eseguito il blocco “else” della struttura, l'n+1-esimo blocco di istruzioni. Con il linguaggio della Bash questa struttura viene implementata introducendo una o più istruzioni “elif” in una struttura condizionale, ottenendo un costrutto del tipo “if ... then ... elif ... then ... else ... fi”; la struttura condizionale ottenuta concatenando più istruzioni di valutazione delle condizioni assume la seguente forma:
1 if lista di comandi; then
2 lista di comandi;
3 elif lista di comandi; then
4 lista di comandi;
5 ...
6 else
7 lista di comandi;
8 fi
Diagramma di controllo delle istruzioni per la definizione di una struttura di controllo condizionale a cascata: “if ... then ... elif ... then ... else ... fi”
Supponiamo ad esempio di dover predisporre uno script che esegue operazioni diverse in base al parametro passato sulla riga di comando. Un esempio di uno script di questo genere è il seguente (memorizzato nel file “chi.sh”):
1 #!/bin/bash
2 opzione=$1
3 echo "Il personaggio indicato e' $opzione"
4 if [[ $opzione == pippo ]]; then
5 echo "Pippo e' il miglior amico di Topolino"
6 elif [[ $opzione == pluto ]]; then
7 echo "Pluto e' il cane di Topolino"
8 elif [[ $opzione == minnie ]]; then
9 echo "Minnie e' la fidanzata di Topolino"
10 else
11 echo "Questo personaggio non lo conosco!"
12 fi
Il parametro passato sulla riga di comando viene memorizzato nella variabile opzione (riga 2). Quindi vengono valutate in cascata alcune condizioni: ciascuna di queste (righe 4, 6 e 8) non è altro che il confronto del valore della variabile opzione con una stringa costante. Se nessuna delle tre condizioni è verificata, viene eseguito il comando che segue l'istruzione “else”. Un esempio di esecuzione dello script è riportato di seguito:
$ ./chi.sh pluto
Il personaggio indicato e' pluto
Pluto e' il cane di Topolino
$ ./chi.sh paperino
Il personaggio indicato e' paperino
Questo personaggio non lo conosco!
In questi casi, ossia quando le condizioni da valutare in cascata sono costituite da semplici confronti tra il valore di una variabile ed un insieme di stringhe, l'istruzione “case” offre una struttura sintattica più semplice e chiara, appositamente disegnata per questo tipo di operazioni. Il blocco condizionale in questo caso si apre con l'istruzione “case” e si chiude con la parola chiave “esac”; la struttura delle istruzioni è la seguente:
1 case variabile in
2 ( pattern | pattern | ... | pattern )
3 istruzioni;;
4 ( pattern | pattern | ... | pattern )
5 istruzioni;;
6 ...
7 esac
Se il valore della variabile riportata dopo l'istruzione “case” corrisponde con uno dei pattern riportati tra parentesi tonde, viene eseguito il blocco di istruzioni corrispondenti. Ad esempio, lo script precedente, potrebbe essere riscritto come segue, senza alterarne minimamente il funzionamento:
1 #!/bin/bash
2 opzione=$1
3 echo "Il personaggio indicato e' $opzione"
4 case $opzione in
5 ( pippo )
6 echo "Pippo e' il miglior amico di Topolino";;
7 ( pluto )
8 echo "Pluto e' il cane di Topolino";;
9 ( minnie )
10 echo "Minnie e' la fidanzata di Topolino";;
11 ( * )
12 echo "Questo personaggio non lo conosco!"
13 esac
È utile osservare che il caso di default, quello che identifica il blocco di istruzioni che viene eseguito nel caso in cui nessuna delle condizioni sia risultata valida, è implementato riportando tra parentesi un pattern che coincide con qualsiasi stringa, costituito dall'espressione “*” (asterisco).
Per implementare algoritmi che svolgano un numero elevato di operazioni con uno script di poche righe di codice, spesso si ricorre alle strutture di controllo iterative. Tali istruzioni permettono di ripetere un certo blocco di comandi più volte, fino a quando non risulta verificata una determinata espressione logica codificata nell'istruzione di controllo della struttura iterativa.
Bash mette a disposizione tre istruzioni principali per la realizzazione di iterazioni (cicli): le istruzioni “while”, “until” e “for”. Dal punto di vista algoritmico, tutte e tre implementano una struttura in cui, all'inizio del blocco di istruzioni da ripetere, viene valutata una condizione logica (spesso il return code di un'istruzione) e, a valle di tale valutazione, viene eseguita un'iterazione del ciclo, ovvero viene terminata definitivamente l'esecuzione del ciclo stesso.
L'istruzione “while” esegue un blocco di istruzioni delimitato dalle parole chiave “do” e “done” se l'istruzione di controllo che segue l'istruzione “while” restituisce il valore “vero” (exit status 0). Dal punto di vista algoritmico una struttura iterativa rispetta il seguente schema generale, rappresentato anche dal diagramma di flusso riportato in Figura:
1. fintanto che l'istruzione di controllo restituisce il valore 0 (vero) ripeti:
2. istruzioni
3. fine-ciclo
Diagramma di flusso della struttura di controllo iterativa implementata con l'istruzione while
La sintassi dell'istruzione “while” è la seguente:
1 while istruzione di controllo
2 do
3 istruzioni
4 done
Viene valutato il return code dell'istruzione di controllo che segue il “while” e, se il valore restituito è vero (0) vengono eseguite le istruzioni delimitate dalle parole-chiave “do” e “done”, quindi viene eseguita nuovamente l'istruzione di controllo e ne viene valutato il return code. Il ciclo termina quando la valutazione della condizione restituisce il valore falso (un valore numerico diverso da 0): in tal caso l'esecuzione dello script prosegue con la prima istruzione successiva alla parola chiave “done”.
Spesso l'istruzione di controllo che regola l'iterazione del ciclo viene realizzata con un'espressione del tipo “[ condizione ]”, che permette di valutare un'espressione logica arbitraria utilizzando gli operatori di confronto visti nel Capitolo 2. Il simbolo “[” è un comando interno della Bash equivalente al comando “test”, che permette di calcolare il valore “booleano” dell'espressione che segue, che termina con il carattere “]” (carattere che deve essere usato solo se si usa il comando “[” e non se si usa “test”).
Dal punto di vista sintattico, precisiamo che deve essere presente uno spazio tra le parentesi quadrate e la condizione. Inoltre è possibile riportare la parola chiave “do” sulla stessa riga dell'istruzione “while”, ma in tal caso è necessario inserire un punto e virgola tra la parentesi quadra chiusa e l'istruzione “do”:
1 while [ condizione ]; do
2 istruzioni
3 done
Il seguente esempio (multipli.sh) stampa i primi k multipli dell'intero n: n e k sono due numeri riportati sulla linea di comando:
1 #!/bin/bash
2 # Visualizza i primi k multipli di n
3 n=$1
4 k=$2
5 echo "Ecco i primi $k multipli di $n:"
6 i=1
7 while [ $i -le $k ]; do
8 ((x=n*i))
9 echo -n "$x "
10 ((i++))
11 done
12 echo "Fatto!"
Con le istruzioni a riga 3 e 4 vengono assegnati il primo ed il secondo parametro riportati sulla linea di comando (rappresentati dalle variabili $1 e $2) rispettivamente alle variabili n e k. Quindi, a riga 6, viene inizializzato il valore della variabile i con cui si terrà il conto del numero di multipli di n che sono stati calcolati. La condizione che regola l'esecuzione del ciclo mediante l'istruzione “while” (riga 7) confronta il valore della variabile i con quello della variabile k, richiedendo che il primo sia minore o uguale al secondo (“-le” less or equal, minore o uguale). Se la condizione risulta vera (se i è minore o uguale a k) allora vengono eseguite le istruzioni delimitate da “do” e “done”. A riga 8 viene calcolato il valore dell'i-esimo multiplo di n, a riga 9 viene stampato e a riga 10 viene incrementato di uno il valore della variabile i. Quindi si torna a riga 7 per eseguire nuovamente il test della condizione e le istruzioni di calcolo del multiplo vengono ripetuto fino a quando il valore di i non supera quello di k. Solo allora verrà interrotta l'esecuzione del ciclo e lo script terminerà dopo aver eseguito l'istruzione a riga 12. Un esempio di output prodotto dallo script è riportato di seguito:
$ ./multipli.sh 3 5
Ecco i primi 5 multipli di 3:
3 6 9 12 15 Fatto!
È possibile interrompere “bruscamente” l'esecuzione di un ciclo anche utilizzando l'istruzione “break”. Questo comando indica alla shell di interrompere l'esecuzione del ciclo entro cui si trova il comando stesso, proseguendo l'esecuzione dello script con la prima istruzione successiva al blocco “do-done”. Il ciclo viene interrotto a prescindere dal valore di vero o falso della condizione riportata con l'istruzione “while”. Di fatto si tratta di un “salto incondizionato” alla prima istruzione fuori dal ciclo e per questo motivo contravviene alle regole fondamentali della programmazione strutturata. Pertanto se ne sconsiglia l'uso, perché rende più confusi e meno manutenibili gli script shell. È pur vero, d'altra parte, che uno shell script è costituito in genere da poche righe di codice e quindi spesso la compattezza dello script è tale che la sua manutenibilità è comunque garantita.
L'istruzione “until” consente di implementare strutture algoritmiche iterative analoghe a quelle che è possibile realizzare con l'istruzione “while”: la differenza tra le due istruzioni consiste nel fatto che la condizione riportata dopo la parola chiave “until”, nel caso in cui risulti falsa produce l'esecuzione delle istruzioni delimitate dalle parole chiave “do” e “done”, mentre, quando risulta vera, il ciclo ha termine e l'esecuzione dello script prosegue con la prima istruzione successiva alla parola chiave “done”. Il comportamento dell'istruzione “until” può essere schematizzato con il diagramma di flusso riportato in Figura: è evidente, quindi, che la valutazione della condizione che controlla l'esecuzione del ciclo, ha un effetto opposto a quello che si ha con l'istruzione “while”.
Diagramma di flusso della struttura di controllo iterativa implementata con l'istruzione “until”
La sintassi dell'istruzione “until” è del tutto analoga a quella dell'istruzione “while”:
1 until istruzioni di controllo ; do
2 istruzioni
3 done
Anche in questo caso le parole chiave “do” e “done” delimitano le istruzioni che costituiscono il corpo del ciclo e che vengono eseguite se il return code dell'istruzione di controllo è diverso da zero (valore logico falso); dopo aver eseguito le istruzioni del blocco del ciclo, viene eseguita nuovamente l'istruzione di controllo: in base al valore del return code restituito vengono eseguite le istruzioni del blocco fino a quando la verifica della condizione non avrà valore vero.
La parola chiave “do” può essere riportata sulla riga successiva a quella dell'istruzione “until”, oppure sulla stessa riga; in quest'ultimo caso deve essere usato un punto e virgola per separarla dall'istruzione che la precede.
Lo stesso script riportato nelle pagine precedenti, che esegue il calcolo dei primi k multipli di un intero n, può quindi essere riscritto utilizzando l'istruzione “until” invece dell'istruzione “while”, come riportato di seguito. Naturalmente l'output prodotto dallo script è identico a quello prodotto dallo script precedente.
1 #!/bin/bash
2 # Visualizza i primi k multipli di n
3 n=$1
4 k=$2
5 echo "Ecco i primi $k multipli di $n:"
6 i=1
7 until [ $i -gt $k ]; do
8 ((x=n*i))
9 echo -n "$x "
10 ((i++))
11 done
12 echo "Fatto!"
La differenza tra i due script è concentrata nella riga 7 dove in un caso viene usata l'istruzione “while” e nell'altro viene usata l'istruzione “until”; per ottenere lo stesso risultato è stata quindi modificata la condizione riportata fra parentesi quadrate: nello script che utilizza l'istruzione “until” il ciclo prosegue fintanto che la condizione i>k risulta falsa; quando il valore di i raggiunge il valore di k, la condizione diventa vera, il ciclo termina e lo script prosegue con l'esecuzione dell'istruzione a riga 12.
Spesso, come negli esempi precedenti, l'iterazione del ciclo è controllata mediante una variabile che “tiene il conto” del numero di iterazioni effettuate: la variabile in questione, assume un valore iniziale e, prima dell'esecuzione di ogni iterazione, viene controllato che il valore della variabile stessa non abbia superato una soglia prefissata; al termine dell'esecuzione del blocco di istruzioni che costituiscono il corpo del ciclo, prima di ripetere il controllo sul valore della variabile “contatore”, viene eseguito l'incremento del contatore stesso.
Queste operazioni possono essere implementate utilizzando le istruzioni “while” e “until”, come effettivamente avviene nei due esempi precedenti. In entrambi gli script, infatti, il ciclo è controllato dalla variabile contatore i: a riga 6 viene inizializzato il valore del contatore (i=1), a riga 7 viene confrontato il valore di i con quello della soglia k e, al termine del blocco di istruzioni da ripetere, a riga 10 viene incrementata la variabile i. Il diagramma di flusso riportato in Figura schematizza la struttura di un ciclo di questo genere.
Diagramma di flusso della struttura di controllo iterativa basata su una variabile contatore, che è possibile implementare con l'istruzione “for”
L'istruzione “for” consente in modo molto diretto di implementare una struttura di controllo iterativa di questo tipo, basata su un contatore. Una delle forme con cui può essere utilizzata l'istruzione “for” è derivata direttamente dal linguaggio C e prevede tre parametri riportati fra doppie parentesi tonde e separati da un punto e virgola:
1 for (( espr1 ; espr2 ; espr3 ))
2 do
3 istruzioni
4 done
La prima espressione (espr1) generalmente è l'istruzione con cui viene inizializzata una variabile contatore; la seconda espressione (espr2) è la condizione con cui si verifica se il contatore ha raggiunto o superato la soglia: se la condizione restituisce il valore vero viene eseguito il blocco di istruzioni delimitate dalle parole chiave “do” e “done”, altrimenti il ciclo termina e lo script prosegue con l'istruzione immediatamente successiva al “done”; la terza espressione (espr3), infine, è l'istruzione con cui viene incrementato (o decrementato) il valore del contatore.
Di seguito riportiamo l'esempio del calcolo dei primi k multipli di n, implementato con l'istruzione “for”.
1 #!/bin/bash
2 # Visualizza i primi k multipli di n
3 n=$1
4 k=$2
5 echo "Ecco i primi $k multipli di $n:"
6 for (( i=1 ; i <= k; i++ ))
7 do
8 ((x=n*i))
9 echo -n "$x "
10 done
11 echo "Fatto!"
Il valore del contatore può essere modificato a piacere ad ogni iterazione del ciclo, con una qualsiasi istruzione di assegnazione di un valore alla variabile contatore utilizzata al posto di espr3. Nell'esempio di seguito viene eseguito il “conto alla rovescia”, che visualizza i numeri 10, 9, 8, ..., 3, 2, 1:
1 #!/bin/bash
2 # Conto alla rovescia
3 for (( i=10 ; i > 0; i-- ))
4 do
5 echo -n "$i "
6 sleep 1
7 done
8 echo "Bum!"
Il contatore viene inizializzato con il valore 10, quindi viene eseguito il blocco di istruzioni delimitate da “do-done” fintanto che il valore della variabile i è maggiore di zero; alla fine dell'esecuzione di ogni blocco di istruzioni viene decrementato di uno il valore del contatore (i--). Il comando esterno “sleep” (riga 6) introduce una pausa pari al numero di secondi indicati come parametro (un secondo nell'esempio precedente).
In generale l'istruzione “for” consente di ripetere un blocco istruzioni al variare di una variabile di controllo (il contatore negli esempi precedenti) in una lista di valori o di un intervallo numerico riportati come argomento dell'istruzione “for”. La lista di valori può essere espressa in forma “statica”, riportando i valori uno di seguito all'altro, separati da spazi, oppure come output di un altro comando o, infine, come insieme di numeri interi descritto mediante gli estremi dell'insieme stesso. In tutti e tre i casi l'istruzione “for” viene integrata dalla parola chiave “in” che separa la variabile dall'elenco dei valori che questa deve assumere:
1 for variabile in lista-di-valori
2 do
3 istruzioni
4 done
Nell'esempio seguente viene fornito un insieme di valori “statici” per la variabile, riportati direttamente nel codice sorgente dello script:
1 #!/bin/bash
2 # Stampa un elenco di animali
3 for animale in cane gatto 'orso bianco' topo
4 do
5 echo $animale
6 done
Ad ogni iterazione del ciclo la variabile a assume uno dei valori riportati nell'elenco che segue la parola chiave “in”. Quando non ci sono altri valori da assegnare alla variabile, il ciclo termina.
I valori della lista possono anche essere generati da un comando, come nell'esempio seguente in cui si suppone che il file “animali.txt” contenga i nomi di un insieme di animali, uno per ogni riga del file:
1 #!/bin/bash
2 # Stampa un elenco di animali, seconda versione
3 echo "Un elenco di animali, in ordine alfabetico:"
4 for animale in $( sort animali.txt )
5 do
6 echo $animale
7 done
La variabile speciale IFS contiene il carattere utilizzato dalla Bash come separatore degli elementi di una lista. Di default il separatore degli elementi di una lista è un qualsiasi simbolo di spaziatura (lo spazio, una tabulazione o un carattere di new line), ma, se occorre, è possibile ridefinirlo impostando un valore arbitrario per la variabile IFS.
Con Bash versione 3.0 e le successive, nel caso in cui la lista di valori da assegnare alla variabile che controlla l'esecuzione del ciclo sia un insieme di numeri interi,}} in ordine crescente, allora l'elenco può essere espresso utilizzando le parentesi graffe per delimitare gli estremi dell'intervallo e specificando il valore minimo e il valore massimo, separati da una coppia di punti: {min..max}. In questo modo la variabile assumerà tutti i valori dell'insieme {min, min+1, min+2, ..., max}. Ad esempio l'espressione “{4..7}” genera la sequenza di numeri interi 4, 5, 6, 7.
A partire dalla versione 4.0 di Bash, si può anche esprimere l'incremento della variabile,}} nel caso in cui non si desideri usare tutti i valori interi dell'intervallo; in tal caso si può esprimere il valore dell'incremento di seguito al valore max, separandolo con una coppia di punti: {min..max..inc}. In questo caso la variabile assumerà la seguente successione di valori:
min, min+inc, min+2 inc, min+3 inc, ...
fino a quando non sarà superato il valore max. L'espressione “{1..15..3}”, ad esempio, genera la sequenza di valori 1, 4, 7, 10, 13.
Il seguente script (che funziona solo con Bash versione 4.0 o successive) visualizza i primi 10 multipli (la tabellina) del numero intero riportato come argomento sulla linea di comando:
1 #!/bin/bash
2 # Tabellina del ...
3 n=$1
4 ((m=n*10))
5 echo "Tabellina del $n:"
6 for x in {$n..$m..$n}; do
7 echo -n "$x "
8 done
9 echo "Fine!"
Sui sistemi dotati di versioni precedenti della Bash può essere presente il comando esterno “seq” che consente di generare sequenze di numeri interi. Il comando “seq” accetta uno, due o tre parametri: con un solo parametro n visualizza in output la sequenza di numeri 1, 2, ..., n; se i parametri sono invece due, ad esempio n ed m, viene generata la sequenza n, n+1, n+2, ..., m. Infine, se vengono forniti tre parametri n, k ed m, viene prodotta la sequenza n, n+k, n+2k, n+3k, ..., fino a superare il valore di soglia m.
Utilizzando il comando “seq” possiamo riscrivere come segue lo script precedente che stampa la “tabellina” del numero fornito come argomento sulla linea di comando:
1 #!/bin/bash
2 # Tabellina del ... con il comando seq
3 n=$1
4 ((m=n*10))
5 echo "Tabellina del $n:"
6 for x in $( seq $n $n $m )
7 do
8 echo -n "$x "
9 done
10 echo "Fine!"
L'output prodotto da quest'ultimo script è identico a quello generato dallo script precedente, ma, se possibile, si consiglia di evitare il ricorso al comando esterno “seq”, che rende meno efficiente lo script.
$ ./tabellina.sh 7
Tabellina del 7:
7 14 21 28 35 42 49 56 63 70 Fine!
L'ultima istruzione per l'implementazione di strutture di controllo iterative è di carattere meno generale di quelle viste nelle pagine precedenti. L'istruzione “select” consente di visualizzare un menù di scelte numerate (da 1 a n) che l'utente può selezionare interattivamente digitando il numero corrispondente all'opzione desiderata. Dopo aver eseguito le istruzioni contenute nel blocco delimitato dalle parole chiave “do-done”, viene nuovamente presentato il prompt e la shell attende che l'utente compia una nuova scelta. Se l'utente batte il tasto Invio/Enter senza digitare un numero corrispondente ad un'opzione, viene visualizzato nuovamente l'intero menù di scelte. Il ciclo viene ripetuto continuamente, fino a quando non viene interrotto mediante l'istruzione “break”.
In pratica l'istruzione “select” implementa una procedura che può essere schematizzata nei seguenti passi:
1. visualizza su standard output le opzioni del menù, numerandole da 1 a n
2. visualizza un prompt e attende che l'utente digiti un numero
3. se l'utente ha selezionato una voce del menù allora esegue le istruzioni delimitate da “do-done”
4. altrimenti visualizza nuovamente il menù
5. ritorna al passo 2
L'istruzione “select” fa uso di alcune variabili speciali per la gestione dell'input/output: la variabile PS3 contiene la stringa utilizzata come prompt per acquisire la scelta dell'utente; la variabile REPLY contiene il valore numerico dell'opzione di menù selezionata dall'utente.
Il seguente esempio aiuta a chiarire il funzionamento dell'istruzione “select”.
1 #!/bin/bash
2 PS3='--> '
3 echo "Menu' principale"
4 select s in Primo Secondo Quit; do
5 echo "Voce di menu' n. $REPLY"
6 case $s in
7 ( Primo )
8 echo "Hai selezionato la prima opzione!";;
9 ( Secondo )
10 echo "Hai selezionato la seconda opzione!";;
11 ( Quit )
12 echo "Hai selezionato la terza opzione!"
13 break;;
14 ( * )
15 echo "Questa opzione non e' prevista.";;
16 esac
17 echo "Hai scelto '$s'!";
18 done
19 echo "Termine dello script"
Lo script visualizza un menù con tre opzioni descritte dalle voci della lista (“Primo”, “Secondo” e “Quit”) riportata di seguito all'istruzione “select”; ciascuna voce corrisponde ad un numero intero positivo 1, 2, 3, ... in base all'ordine con cui compare l'elemento nella lista. Ad ogni iterazione del ciclo la Bash visualizza il prompt definito impostando la variabile PS3 a riga 2. Il menù di opzioni viene visualizzato prima del prompt la prima volta che viene eseguito il ciclo ed ogni volta che l'utente effettuerà una scelta nulla, battendo semplicemente il tasto Invio/Enter senza digitare alcuna opzione.
Nell'esempio riportato nello script precedente, selezionando una delle prime due voci del menù viene semplicemente visualizzato un messaggio; selezionando la terza opzione (Quit) viene visualizzato lo stesso messaggio, ma poi viene anche interrotto il ciclo di gestione del menù, con l'istruzione “break” presente a riga 13. Selezionando un'opzione non gestita dall'istruzione “case”, viene eseguito il blocco di istruzioni di default a riga 15. Un esempio di output prodotto dall'esecuzione dello script precedente è riportato di seguito:
$ ./menu.sh
Menu' principale
1) Primo
2) Secondo
3) Quit
--> 1
Voce di menu' n. 1
Hai selezionato la prima opzione!
Hai scelto 'Primo'!
--> 5
Voce di menu' n. 5
Questa opzione non e' prevista.
Hai scelto ”!
-->
1) Primo
2) Secondo
3) Quit
--> 3
Voce di menu' n. 3
Hai selezionato la terza opzione!
Termine dello script
Dall'esempio si può vedere come il valore assunto dalla variabile s, utilizzata a riga 4 dello script nell'istruzione “select”, corrisponda all'elemento della lista di opzioni scelto dall'utente digitando il numero della voce di menù corrispondente, che infatti viene visualizzato con l'istruzione a riga 17 che utilizza la variabile s; viceversa la variabile REPLY assume come valore proprio il numero dell'opzione selezionata dall'utente, visualizzato con l'istruzione a riga 5 che utilizza proprio la variabile REPLY.
Comandi interni, esterni e composti
Variabili, varibili d'ambiente e variabili speciali