3.3 Lezione 2
Certificazione: |
Linux Essentials |
---|---|
Versione: |
1.6 |
Argomento: |
3 Il Potere della Command Line |
Obiettivo: |
3.3 Trasformare i Comandi in uno Script |
Lezione: |
2 di 2 |
Introduzione
Nell’ultima lezione abbiamo utilizzato questo semplice esempio per mostrare uno script Bash:
#!/bin/bash # A simple script to greet a single user. if [ $# -eq 1 ] then username=$1 echo "Hello $username!" else echo "Please enter only one argument." fi echo "Number of arguments: $#."
-
Tutti gli script dovrebbero iniziare con uno shebang che definisce il percorso dell’interprete.
-
Tutti gli script dovrebbero includere dei commenti per descrivere il loro utilizzo.
-
Questo specifico script funziona con un argomento che gli viene passato al momento della sua invocazione.
-
Questo script contiene una istruzione if che testa le condizioni della variabile interna (built-in)
$#
che contiene il numero di argomenti. -
Se il numero di argomenti passati allo script è uguale a 1, allora il valore del primo argomento viene assegnato a una nuova variabile chiamata
username
e lo script stampa un saluto all’utente. In caso contrario, viene visualizzato un messaggio di errore. -
Infine, lo script stampa il numero di argomenti. Questo è utile per il debug.
Questo è un esempio utile per iniziare a spiegare alcune delle altre caratteristiche degli script Bash.
Codici di Uscita
Avrai notato che il nostro script ha due possibili stati: o stampa "Hello <user>!"
o stampa un messaggio di errore. Questo è piuttosto normale per molte delle nostre principali utility. Considera cat
, con cui senza dubbio stai diventando molto familiare.
Confrontiamo ora una situazione in cui cat
viene eseguito con successo con una in cui dà errore. Ricorda che il nostro esempio sopra riportato è uno script chiamato new_script.sh
.
$ cat -n new_script.sh 1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 else 11 echo "Please enter only one argument." 12 fi 13 echo "Number of arguments: $#."
Questo comando viene eseguito con successo; nota che l’opzione -n
ha anche stampato i numeri di riga. Questi sono molto utili durante il debug degli script, ma tieni presente che non fanno parte dello script.
Controlliamo ora il valore di una nuova variabile interna (built-in): $?
. Per il momento, nota solamente il valore di output:
$ echo $? 0
Consideriamo ora una situazione in cui cat
non viene eseguito con successo. Per prima cosa osserviamo il messaggio di errore e poi controlliamo il valore di $?
.
$ cat -n dummyfile.sh cat: dummyfile.sh: No such file or directory $ echo $? 1
La spiegazione per questo comportamento è la seguente: qualsiasi esecuzione dell’utility cat
restituisce un codice di uscita. Un codice di uscita indica se il comando è stato eseguito con successo o se si è verificato un errore. Un codice di uscita pari a zero indica che il comando è stato completato correttamente. Questo è vero per quasi tutti i comandi Linux con cui hai a che fare. Qualsiasi altro codice di uscita indica un errore di qualche tipo. Il codice di uscita dell'ultimo comando eseguito è memorizzato nella variabile $?
.
I codici di uscita, di solito, non sono controllati dagli utenti in carne e ossa, ma sono molto utili durante la redazione di script. Considera uno script che copia i file su un’unità di rete remota: esistono molte cause per le quali l’attività di copia potrebbe non andare a buon fine: per esempio, la nostra macchina locale potrebbe non essere collegata alla rete o l’unità remota potrebbe essere piena. Controllando il codice di uscita della nostra utility di copia possiamo avvisare l’utente di eventuali problemi durante l’esecuzione dello script.
È buona norma implementare i codici di uscita ed è ciò che faremo ora. Abbiamo due percorsi nel nostro script: un successo e un errore. Usiamo zero per indicare il successo e uno per indicare l’errore.
1 #!/bin/bash 2 3 # A simple script to greet a single user. 4 5 if [ $# -eq 1 ] 6 then 7 username=$1 8 9 echo "Hello $username!" 10 exit 0 11 else 12 echo "Please enter only one argument." 13 exit 1 14 fi 15 echo "Number of arguments: $#."
$ ./new_script.sh Carol Hello Carol! $ echo $? 0
Nota che il comando echo
alla riga 15 viene completamente ignorato. Infatti, exit
termina immediatamente lo script; quindi questa riga non viene mai raggiunta.
Gestire più Argomenti
Per ora il nostro script può gestire un solo nome utente alla volta. Qualsiasi numero di argomenti diverso da 1 causerà un errore. Esaminiamo come poter rendere questo script più versatile.
Il primo istinto di un utente potrebbe essere quello di utilizzare più variabili posizionali come, per esempio, $2
, $3
e così via. Sfortunatamente, non possiamo prevedere il numero di argomenti che un utente potrebbe voler utilizzare. Per risolvere questo problema può essere utile introdurre altre variabili interne (built-in).
Modifichiamo la logica del nostro script: in caso ci siano 0 argomenti, dovrebbe essere generato un errore, ma un qualsiasi altro numero di argomenti renderebbe lo script corretto. Questo nuovo script si chiamerà friendly2.sh
.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo "Hello $@!" 11 exit 0 12 fi
$ ./friendly2.sh Carol Dave Henry Hello Carol Dave Henry!
Esistono due variabili interne (built-in) che contengono tutti gli argomenti passati allo script: $@
e $*
. Nella maggior parte dei casi entrambe si comportano allo stesso modo. Bash analizza gli argomenti e li separa quando incontra uno spazio. In pratica, il contenuto di $@
ha questo aspetto:
|
|
|
|
|
|
Se hai familiarità con altri linguaggi di programmazione potresti conoscere questo tipo di variabile come un array. Gli array in Bash possono essere creati semplicemente inserendo uno spazio tra gli elementi, come per la variabile FILES
nello script arraytest
qui sotto:
FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot"
La variabile contiene un elenco di diversi elementi. Per ora non è molto utile, poichè non abbiamo ancora introdotto un modo per gestire singolarmente questi elementi.
Cicli For
Facciamo riferimento all’esempio arraytest
mostrato in precedenza. Se ricordi, in questo esempio abbiamo specificato un nostro array chiamato FILES
. Ciò di cui abbiamo bisogno è un modo per “spacchettare” questa variabile e accedere a ogni singolo valore, uno dopo l’altro. Per fare questo, useremo una struttura chiamata ciclo for, che è presente in tutti i linguaggi di programmazione. Faremo riferimento a due variabili: una è l’intervallo e l’altra è il valore individuale su cui stiamo attualmente lavorando. Questo è lo script nella sua interezza:
#!/bin/bash FILES="/usr/sbin/accept /usr/sbin/pwck/ usr/sbin/chroot" for file in $FILES do ls -lh $file done
$ ./arraytest lrwxrwxrwx 1 root root 10 Apr 24 11:02 /usr/sbin/accept -> cupsaccept -rwxr-xr-x 1 root root 54K Mar 22 14:32 /usr/sbin/pwck -rwxr-xr-x 1 root root 43K Jan 14 07:17 /usr/sbin/chroot
Se fai nuovamente riferimento all’esempio friendly2.sh
sopra riportato noterai che stiamo lavorando con una serie di valori contenuti all’interno di un’unica variabile $@
. Per maggiore chiarezza chiameremo quest’ultima variabile username
. Il nostro script ora ha questo aspetto:
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo "Hello $username!" 13 done 14 exit 0 15 fi
Ricorda che la variabile che definisci può avere un qualunque nome e che tutte le righe all’interno di do… done
verranno eseguite una volta per ogni elemento dell’array. Osserviamo l’output del nostro script:
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry!
Supponiamo ora di voler rendere il nostro output un po' più umano, facendo in modo che il saluto appaia su un’unica riga.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 echo -n "Hello $1" 11 shift 12 for username in $@ 13 do 14 echo -n ", and $username" 15 done 16 echo "!" 17 exit 0 18 fi
Un paio di note:
-
L’uso di
-n
conecho
elimina il carattere di nuova riga dopo la stampa. Questo significa che tutto l’output verrà stampato sulla stessa riga e il carattere di nuova riga verrà stampato solo dopo il!
della riga 16. -
Il comando
shift
rimuove il primo elemento del nostro array, in modo tale che questo:
|
|
|
|
|
|
diventi questo:
|
|
|
|
Diamo un’occhiata all’output:
$ ./friendly2.sh Carol Hello Carol! $ ./friendly2.sh Carol Dave Henry Hello Carol, and Dave, and Henry!
Usare le Espressioni Regolari per Eseguire il Controllo degli Errori
Potremmo voler verificare tutti gli argomenti che inserisce l’utente. Per esempio, potremmo verificare che tutti i nomi passati a friendly2.sh
contengano solo lettere, in modo tale che qualsiasi carattere speciale o numero causi un errore. Per eseguire questo controllo degli errori useremo grep
.
Ricorda che possiamo usare le espressioni regolari con grep
.
$ echo Animal | grep "^[A-Za-z]*$" Animal $ echo $? 0
$ echo 4n1ml | grep "^[A-Za-z]*$" $ echo $? 1
^
e $
indicano rispettivamente l’inizio e la fine della riga. [A-Za-z]
indica un intervallo di lettere, maiuscole o minuscole. *
è un quantificatore che modifica il nostro intervallo di lettere in modo che si abbia corrispondenza per 0 o un qualsiasi numero di lettere. In sintesi: il nostro grep
verrà eseguito con successo se l’input è costituito solo da lettere, e darà errore in caso contrario.
La prossima cosa da notare è che grep
restituisce i codici di uscita in base al fatto che ci sia o meno corrispondenza con quanto inserito. Se c’è corrispondenza restituisce 0
, mentre se non c’è corrispondenza restituisce 1
. Possiamo quindi utilizzarlo per testare gli argomenti all’interno del nostro script.
1 #!/bin/bash 2 3 # a friendly script to greet users 4 5 if [ $# -eq 0 ] 6 then 7 echo "Please enter at least one user to greet." 8 exit 1 9 else 10 for username in $@ 11 do 12 echo $username | grep "^[A-Za-z]*$" > /dev/null 13 if [ $? -eq 1 ] 14 then 15 echo "ERROR: Names must only contains letters." 16 exit 2 17 else 18 echo "Hello $username!" 19 fi 20 done 21 exit 0 22 fi
Nella riga 12 redirigiamo lo standard output verso /dev/null
, il che rappresenta un semplice modo per sopprimere tale output. Non vogliamo vedere alcun output del comando grep
: vogliamo solo testare il suo codice di uscita, cosa che facciamo nella riga 13. Nota inoltre che stiamo usando un codice di uscita pari a 2
per indicare un argomento non valido. In genere è buona norma utilizzare codici di uscita diversi per indicare errori diversi: in questo modo, un utente esperto può utilizzare questi codici di uscita per individuare e risolvere i problemi.
$ ./friendly2.sh Carol Dave Henry Hello Carol! Hello Dave! Hello Henry! $ ./friendly2.sh 42 Carol Dave Henry ERROR: Names must only contains letters. $ echo $? 2
Esercizi Guidati
-
Osserva il contenuto di
script1.sh
qui sotto riportato:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Quale sarà l’output dei seguenti comandi?
-
./script1.sh
-
echo $?
-
./script1.sh cake
-
echo $?
-
./script1.sh CAKE
-
echo $?
-
-
Osserva il contenuto del file
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Descrivi lo scopo di questo script.
Esercizi Esplorativi
-
Crea uno script che accetti un numero qualsiasi di argomenti dall’utente e stampi solamente gli argomenti che hanno come valore un numero maggiore di 10.
Sommario
In questa lezione hai imparato:
-
Cosa sono i codici di uscita, cosa significano e come si implementano;
-
Come controllare il codice di uscita di un comando;
-
Cosa sono i cicli
for
e come usarli con gli array; -
Come usare
grep
, le espressioni regolari e i codici di uscita per controllare l’input dell’utente negli script.
Comandi utilizzati negli esercizi:
shift
-
Rimuove il primo elemento di un array.
Variabili speciali:
$?
-
Contiene il codice di uscita dell’ultimo comando eseguito.
$@
,$*
-
Contiene tutti gli argomenti passati allo script, sotto forma di un array.
Risposte agli Esercizi Guidati
-
Osserva il contenuto di
script1.sh
qui sotto riportato:#!/bin/bash if [ $# -lt 1 ] then echo "This script requires at least 1 argument." exit 1 fi echo $1 | grep "^[A-Z]*$" > /dev/null if [ $? -ne 0 ] then echo "no cake for you!" exit 2 fi echo "here's your cake!" exit 0
Quale sarà l’output dei seguenti comandi?
-
Comando:
./script1.sh
Output:
This script requires at least 1 argument.
-
Comando:
echo $?
Output:
1
-
Comando:
./script1.sh cake
Output:
no cake for you!
-
Comando:
echo $?
Output:
2
-
Comando:
./script1.sh CAKE
Output:
here’s your cake!
-
Comando:
echo $?
Output:
0
-
-
Osserva il contenuto del file
script2.sh
:for filename in $1/*.txt do cp $filename $filename.bak done
Descrivi lo scopo di questo script.
Questo script crea delle copie di backup di tutti i file che terminano con
.txt
che si trovano in una sottodirectory definita nel primo argomento.
Risposte agli Esercizi Esplorativi
-
Crea uno script che accetti un numero qualsiasi di argomenti dall’utente e stampi solamente gli argomenti che hanno come valore un numero maggiore di 10.
#!/bin/bash for i in $@ do echo $i | grep "^[0-9]*$" > /dev/null if [ $? -eq 0 ] then if [ $i -gt 10 ] then echo -n "$i " fi fi done echo ""