Shell - DISCo
Transcript
Shell - DISCo
Shell - Scripting Programming 1 Che cos’è la shell di Linux? La shell è un interprete di comandi. Essa esegue i comandi letti da standard input oppure da file. La shell non fa parte del kernel del sistema operativo, ma usa il kernel per eseguire i comandi. 2 Quale shell? Diverse shell sono disponibili sotto Linux. Le più comuni sono: • BASH (Bourne-Again SHell): è freeware ed è molto comune. • CSH (C SHell): la sua sintassi ed il suo uso sono molto simili al linguaggio C. • KSH (Korn SHell). • TCSH: una versione più avanzata della CSH. 3 Quale shell? Per trovare tutte le shell che sono disponibili sul proprio sistema, si può usare il seguente comando: $ cat /etc/shells Ogni shell ha una diversa sintassi e mette a disposizione un diverso insieme di funzionalità predefinite. In MS-DOS, il nome della shell è COMMAND.COM. Essa è stata concepita per gli stessi scopi, ma è molto meno potente delle shell di Linux. Per sapere quale shell si sta usando, occorre digitare il seguente comando: echo $SHELL 4 Che cos’è uno script di Shell? La definizione più semplice di script di shell è: “ Una serie di comandi di shell scritti in un file di testo “ Successivamente sono stati introdotti alcuni costrutti di controllo tipici di un vero linguaggio di programmazione, al fine di rendere gli script di shell più potenti. 5 Perché usare gli script di Shell? • Permettono di creare nuovi comandi personalizzati. • Permettono di risparmiare tempo (esecuzione di un solo comando, invece di molti comandi in sequenza). • Permettono di automatizzare dei task che devono essere eseguiti con alta frequenza (ad esempio tutti i giorni alla stessa ora). • Permettono di facilitare il ruolo dell’Amministratore di Sistema. 6 Informazioni varie Gli esempi che verranno mostrati in questo corso sono stati eseguiti usando la bash shell ed usando Linux Red Hat versione 6.x+. 7 Getting Started with Shell Programming 8 Come scrivere uno script di shell Per eseguire uno script di shell, i passi sono i seguenti: • Scrivere lo script utilizzando un editore di testi (vi, emacs, xemacs, mcedit, kedit, etc.) • Dopo aver scritto e salvato il file di testo che contiene lo script, settare i permessi di esecuzione per il vostro utente. Il comando da usare è chmod e la sintassi è: $ chmod +x nome-del-file $ chmod 755 nome-del-file (scrittura e lettura ed esecuzione per owner, solo lettura ed esecuzione per gruppo e altri) $ chmod 777 nome-del-file (scrittura, lettura ed esecuzione per tutti) ... • Eseguire lo script con la seguente sintassi: $ ./nome-del-file $ bash nome-del-file $ sh nome-del-file 9 Il primo script di shell Aprire un editore di testo e scrivere: clear echo “Hello World!” Salvare il file col nome s1.sh. (nota: dare l’estensione .sh agli script di shell è una convenzione, ma non un’obbligo: nessuna estensione è espressamente richiesta). Eseguire: $ chmod 755 s1.sh $ ./s1.sh Viene ripulito il terminale, viene scritto Hello World! e, nella riga successiva viene restituito il prompt in modo che l’utente possa digitare il prossimo comando. 10 Commenti Tutte le righe che cominciano con il simbolo “#” sono considerati commenti. Ad esempio, il file s1.sh può essere modificato nel modo seguente: # # Questo e’ il mio primo # script di shell # clear echo “Hello World!” 11 Il secondo script di shell Aprire un file con un editore di testo, chiamarlo s2.sh e scrivere: clear echo "hello $USER" echo "today is: "; date echo "number of user login: "; who | wc -l echo "Calendar: "; cal exit 0 12 Variabili In Linux esistono due tipi di variabile: • Variabili di sistema: create e gestite dal sistema Linux stesso. Questo tipo di variabile viene normalmente chiamata con CARATTERI MAIUSCOLI. • Variabili definite dall’utente (User Defined Variables, UDV): create e gestite dall’utente. Normalmente si indicano con caratteri minuscoli. 13 Variabili Alcune delle variabili di sistema più importanti sono: • BASH, che contiene il path dell’eseguibile della bash shell. • BASH VERSION: la versione della bash shell in uso. • COLUMNS: il numero di colonne dello schermo. • HOME: il path della propria home directory. • LINES: il numero di linee dello schermo. • OSTYPE: il tipo del sistema operativo in uso. 14 • PATH: tutti i path che contengono eseguibili. • PS1: i settaggi che permettono di creare la forma del prompt. • PWD: la directory corrente • SHELL: la shell correntemente in uso con il suo path. • USERNAME: nome dell’utente correntemente loggato sul PC. Per vedere il valore di ognuna di queste variabili usare il comando echo. Ad esempio: $ echo $USERNAME Se ci si dimentica il dollaro dopo USERNAME, stampa la stringa USERNAME. Il dollaro consente di stamparne il valore. 15 User Defined Variables Per definire una variabile utente, la sintassi è: variable-name=value Esempio $ no=10 va bene. $ 10=no NON va bene: i valori devono essere a destra dell’uguale. 10 non è un nome di variabile corretto, perché comincia con una cifra. $ vech=Bus è corretto e definisce una variabile di nome vech il cui valore è Bus. Anche per visualizzare il valore di una USV si può esare il comando echo esattamente come nel caso delle variabili di sistema. 16 Nomi di variabili (regole valide sia per variabili di sistema che per UDV) • Deve cominciare con carattere alfanumerico. • Non deve contenere spazi vuoti. Inoltre, quando si definisce una variabile assegnandole un valore, occorre non mettere gli spazi neanche prima e dopo il simbolo “=”: $ $ $ $ no=10 va bene no =10 NON va bene no= 10 NON va bene no = 10 NON va bene • Le variabili sono “case sensitive”, quindi no è diversa da No. 17 • Si possono definire variabili NULL, ovvero variabili che non hanno alcun valore al momento della loro definizione, come segue: $ vech= $ vech=“ ” • I caratteri ?, * ed altri sono vietati. 18 Ancora sulle variabili Tutte le variabili usate normalmente sono variabili locali. Una variabile locale può essere usata in una shell, ma se si apre un’altra shell, esse non sono più visibili. Esempio $ veicolo=Bus $ echo $veicolo Bus $ /bin/bash $ echo $veicolo <non scrive niente> $ veicolo=Car $ echo $veicolo Car $ exit $ echo $veicolo Bus 19 Variabili globali Per avere variabili di shell globali, (ovvero variabili che nonostante siano state definite in una “vecchia” shell sono accessibile anche dalle “nuove” shells), occorre usare il comando export. Sintassi: export var1 var2 ... varN Esempio $ veicolo = Bus $ echo $veicolo Bus $ export veicolo $ /bin/bash $ echo $veicolo Bus $ exit $ echo $veicolo Bus 20 Aritmetica di shell Si possono scrivere cose del tipo: $ expr op1 operatore op2 e verrà stampato il risultato dell’espressione a sinistra della parola chiave expr. Esempi $ expr 1 + 3 se si digita return (invio), il sistema risponde 4. Altri esempi: expr 2 - 1 expr 10 / 2 expr 20 % 3 expr 10 /∗ 3 (dato che il simbolo ∗ è un simbolo speciale) 21 Ancora aritmetica di shell Si può anche scrivere: $ echo ‘expr 6 + 3‘ e l’effetto è lo stesso (viene stampato 9). Si noti che: • Prima della parola chiave expr e dopo l’espressione si è usato il simbolo ‘ (back quote) e non il simbolo ’ (quote). • Quote, oppure double quote (“), non avrebbero funzionato. 22 Considerazioni sulle virgolette • Double quote (“): tutti i caratteri compresi tra double quotes (eccetto / e $) perdono il loro significato (diventano caratteri facenti parte di una costante di tipo stringa). • Single quote (’): tutto ciò che è interno a single quotes rimane immutato. • Back quote (‘): ciò che è compreso all’interno delle back quotes viene interpretato come un comando da eseguire. 23 Exit Status In conseguenza dell’esecuzione di un comando, Linux, per default, può restituire due valori, a secondo che il comando sia andato a buon fine o meno: • zero: il comando ha avuto successo (terminazione senza errori). • diverso da zero: terminazione con errore. Come si fa a sapere se un comando è terminato con un errore o meno? Linux mette a disposizione una particolare variabile di shell: $? 24 Esempio Supponiamo che un file di nome fileNonEsistente non sia presente sull’hard disk. Se viene digitato il comando: $ rm fileNonEsistente verrà visualizzato un messaggio di errore del tipo: rm: cannot remove ‘fileNonEsistente‘: No such file or directory A questo punto, se viene digitato il comando: $ echo $? viene visualizzato un numero diverso da zero per indicare che c’è stato un errore. Se invece si digita: $ ls $ $? verrà stampato 0 ad indicare che il comando ha avuto successo. 25 Il comando read Viene usato per ottenere un input da tastiera. La sua sintassi è: read var1 var2 ... varN Ad esempio, il seguente script chiede all’utente di digitare un nome e aspetta che l’utente scriva una stringa e poi digiti ENTER (INVIO). A questo punto, ciò che l’utente ha scritto viene memorizzato nella variabile fname e successivamente stampato a video: echo ’’Your first name please: ’’ read fname echo ‘‘Hello $fname !’’ 26 Wild Cards • ∗: matcha qualsiasi stringa o gruppo di caratteri. Esempi: $ ls ∗ visualizza tutti i file nella directory corrente. $ ls ∗a visualizza tutti i file nella directory corrente il cui nome finisce per a. $ ls ut∗.c visualizza tutti i file nella directory corrente il cui nome comincia per ut e che hanno una estensione .c. • ?: matcha un qualsiasi carattere singolo. Esempi: $ ls ? visualizza tutti i file nella directory corrente il cui nome è composto da un solo carattere. $ ls fo? visualizza tutti i file nella directory corrente il cui nome è composto da tre caratteri di cui i primi due sono f ed o. 27 • [...]: matcha uno qualsiasi tra i caratteri inclusi. Esempio: $ ls [abc]* visualizza tutti i file nella directory corrente il cui nome comincia con a, con b, oppure con c. Nota: una coppia di caratteri separati da un simbolo - denota un range: $ ls /bin/[a-c]* visualizza tutti i file nella directory bin il cui nome comincia per a, b o c Analogamente, i simboli ! e “cappuccio” servono per negare: $ ls /bin/[!a-o] $ ls /bin/[^a-o] visualizzano tutti i file nella directory bin che NON cominciano con una lettera compresa tra a ed o. 28 Punto e virgola È possibile mettere più comandi su una linea di comando, separandoli con ; $ date; who prima stampa la data e poi gli utenti loggati. $ date who restituisce un errore perché interpreta who come un parametro del comando date. Se ci sono più stringhe sulla command line senza alcun separatore, Linux interpreta la prima come il comando da eseguire e le altre come i suoi parametri. 29 Parametri di uno script Supponiamo di eseguire: $ myshell foo bar dove myshell è il nome di un file contenente uno script di shell. All’interno dello script, $0 indicherà myshell, $1 indicherà foo (primo parametro), $2 indicherà bar (secondo parametro). (NON si possono assegnare nuovi valori a $1, $2, ...). Esiste una variabile predefinita # il cui valore è il numero di parametri dello script in esecuzione. Ad esempio, nel caso precedente, $# restituisce 2. Al momento, uno script può avere al massimo 9 parametri. 30 Redirezione di input ed output La maggior parte dei comandi visualizzano i risultati sullo standard output (schermo) e prendono input dello standard input (tastiera). Linux consente anche di usare dei files per l’input e l’output. • $ ls > filename l’output di ls viene messo nel file filename. Se il file esisteva già, viene sovrascritto. • ls >> filename l’output di ls viene concatenato in fondo al file filename, se esso esiste, altrimenti esso viene creato e il comando equivale a quello del punto precedente. • cat < myfiles interpreta il contenuto di myfiles come una lista di nomi di files e ne visualizza il contenuto. 31 Esempi Se digitiamo: $ cat > sname Linux si ferma ed aspetta che l’utente digiti il contenuto del nuovo file che si chiamerà sname. Immaginiamo di digitare: martina alessia francesca eleonora Se si digita CONTROL-D, si salva il file sname con questo contenuto. 32 A questo punto, si può digitare il comando: $ sort < sname > sorted names alla fine sorted names contiene i nomi in ordine alfabetico. Altro esempio: $ tr ”[a-z]” ”[A-Z]” < sname > cap names trasforma tutti i nomi in lettere maiuscole e salva il nuovo file con il nome cap names. Alla fine il file cap names contiene: MARTINA ALESSIA FRANCESCA ELEONORA 33 Pipe Il pipe è una tecnica per connettere l’output di un programma con l’input di un altro senza utilizzare file temporanei. La sintassi è: comando1 | comando2 Esempi: $ ls | more l’output del comando ls è dato in input al comando more, in modo tale che esso sia stampato una pagina di terminale alla volta. $ who | sort l’output di who viene dato in input a sort: questo comando stampa la lista di utenti correntemente loggati ordinata alfabeticamente. 34 Pipe $ who | sort > user list user list. come sopra, ma l’output viene messo in un file $ who | wc -l stampa il numero di caratteri“a capo”nella lista di utenti, ovvero stampa il numero di utenti. $ ls -l | wc -l analogamente, stampa il numero di files che sono presenti nella directory corrente. $ who | grep root stampa tutte le linee dell’output di who nelle quali compare la parola root. Il comando grep stampa le linee che matchano un pattern. 35 Filtri Un filtro è un comando Linux che riceve un input, esegue alcune azioni su esso e restituisce un output. Ad esempio, supponiamo di avere un file che si chiama hotel.txt, contenente 100 linee di dati. Da hotel.txt supponiamo di voler stampare i contenuti delle linee da 20 a 30 e memorizzare il risultato in un file chiamati hlist. Il comando per fare ciò è: $ tail +20 < hotel.txt | head -n30 > hlist Qui il comando head è un filtro che prende il suo input dal comando tail (il comando tail comincia a selezionare dalla linea 20 del file hotel.txt), mentre il suo output è rediretto sul file hlist. 36 Filtri Un altro esempio: $ sort < sname | uniq > u name Qui uniq è un filtro che prende in input il risultato del comando sort, mentre il suo output è rediretto nel file u name. 37 Condizioni In script programming, il tipo booleano (che viene usato, ad esempio, per testare le condizioni!) viene rappresentato come segue: • Vero: 0 • Falso: Un qualsiasi valore diverso da zero. Tipico esempio di uso: la terminazione. Se il valore restituito è zero, significa che la terminazione è stata senza errori, se è diverso da zero, significa che c’è stato un errore ed il numero mi dice che tipo di errore. 38 Costrutti per il controllo del flusso 39 Comando condizionale if-then Sintassi: if condizione then comando fi Una condizione può essere: • Una espressione booleana (derivante, ad esempio, da confronto tra due valori). • Una condizione di terminazione di un comando o di un altro script. 40 Esempio Si apra un file di testo di nome showfile e vi si scriva dentro: if cat $1 then echo -e ‘‘\n\nFile $1 trovato e visualizzato con successo’’ fi Si esegua: $ chmod 755 showfile $ ./showfile foo Il nome dello script di shell è showfile ($0) e foo è il suo argomento ($1). 41 Descrizione del precedente esempio Se esiste un file che si chiama foo nella directory dove viene eseguito lo script, allora il comando cat lo visualizza sullo schermo e termina con successo (termina restituendo il valore zero). In questo caso, il valore della condizione che viene testata tramite il comando if è vero, e quindi il comando echo -e “...” viene eseguito. Al contrario, se il file non viene trovato, allora il comando cat non termina con successo (ovvero restituisce un valore diverso da zero) e quindi il comando echo -e “...” non viene eseguito e si passa direttamente all’esecuzione del comando successivo. 42 Esempio Si inserisca il seguente script in un file di nome rmif: if rm $1 then echo ‘‘File $1 cancellato!’’ fi Si esegua: $ chmod 775 rmif Si esegua: $ ./rmif foo Qual’è il comportamento di questo script? 43 Espressioni Fin’ora abbiamo usato il comando if per testare la condizione di terminazione di un comando. Si può anche usare per testare il valore di una espressione. Una espressione nel linguaggio di scripting si può scrivere: • Facendola precedere dalla parola chiave test • Includendola tra parentesi graffe ([]). 44 Esempio Script che controlla se un dato argomento è un numero positivo. Si inserisca il seguente script in un file chiamato ispositive. if test $1 -gt 0 # oppure [$1 -gt 0] then echo ‘‘$1 è un numero positivo’’ fi Esecuzione: $ chmod 755 ispositive $ ./ispositive 5 5 è un numero positivo $ ./is positive -45 $ <non viene scritto nulla!> $ ./ispositive test: -gt unary operator expected 45 Operatori Operatori matematici: -eq : uguale a -ne : diverso da -lt : minore di - le : minore o uguale a - gt : maggiore di -ge : maggiore o uguale a Operatori su stringhe: string1 = string2 : uguaglianza string1 != string2 : diversità string1 : testa se string1 non è nulla, oppure non è definita -n string1 : testa se string1 non è nulla ed esiste -z string1 : testa se string1 è nulla ed esiste 46 Operatori Operatori su file: -s file : testa se file non è vuoto -f file : testa se file esiste e non è una directory -d dir : testa se dir esiste ed è una directory -w file : testa se file è scrivibile -r file : testa se file è read-only -x file : testa se file è eseguibile Operatori logici: !expr : NOT expr1 -a expr2 : AND expr1 -o expr2 : OR 47 Comando condizionale if-then-else Sintassi: if condizione then comando1 else comando2 fi 48 Esempio Aprire un file di testo di nome isnump ed inserire il seguente codice: if [$# -eq 0] then echo ‘‘$0: Devi digitare un numero intero!’’ exit 1 fi if test $1 -gt 0 then echo ‘‘$1 è un numero positivo’’ else echo ‘‘$1 è un numero negativo’’ fi 49 Spiegazione La prima parte dello script controlla se l’utente fornisce o meno un parametro dalla riga di comando. Se non viene fornito nessun parametro, viene stampato un messaggio d’errore. Il comando if, infatti, controlla se il numero di argomenti ($#) passato allo script è uguale (-eq) a zero. Se un qualunque numero di argomenti viene passato allo script, la condizione è falsa e quindi il primo if non ha alcun effetto. 50 Compilare ed eseguire come segue: $ chmod 777 isnump $ ./isnump 5 5 è un numero positivo $ ./isnump -45 -45 è un numero negativo $ ./isnump ./isnump: devi digitare un numero intero! $ ./isnump 0 0 è un numero negativo Per evitare ciò, si può scrivere la condizione come: if test $1 -ge 0. 51 Esempio Si inserisca nel file nestedif.sh il seguente script contenente un if-then-else annidato: sistemaOp=0 echo ‘‘1. Unix (Sun OS)’’ echo ‘‘2. Linux (Red Hat)’’ echo -n ‘‘Seleziona il tuo sistema operativo (1 o 2)’’ read sistemaOp if [$sistemaOp -eq 1] then echo ‘‘Hai scelto Unix (Sun OS)’’ else if [$sistemaOp -eq 2] then echo ‘‘Hai scelto Linux (Red Hat)’’ else echo ‘‘Che cosa? Non ti piace Linux/Unix?!’’ fi fi 52 if-then-else multilivello Sistassi: if condizione_1 then comando_1 elif condizione_2 then comando_2 elif condizione_3 then comando_3 ... elif condizione_n then comando_n else comando_n+1 fi 53 Esempio Sia elf il nome del file contenente il seguente script: if [$1 -gt 0] ; then echo ‘‘$1 è un numero positivo’’ elif [$1 -lt 0] then echo ‘‘$1 è un numero negativo’’ elif [$1 -eq 0] then echo ‘‘$1 è uguale a zero’’ else echo ‘‘Opps! $1 non è un numero!’’ fi Eseguire: $ chmod 755 elf $ ./elf 1 $ ./elf -2 $ ./elf 0 $ ./elf a 54 Loops La bash supporta due tipi di loop: • for • while Istruzioni per costruire un buon loop (valide per qualsiasi linguaggio di programmazione!): • Inizializzare la (o le) variabile (i) usata nella condizione del loop, prima dell’esecuzione del loop. • Ricordarsi che il test della condizione viene eseguito all’inizio di ogni iterazione. • Il “corpo” del loop deve terminare con un comando che modifica il valore della (e) variabile (i) della condizione. 55 Ciclo for. Tipo 1 Sintassi for <nome di variabile> in <lista> do comando done Esempio (in un file di nome testfor): for i in 1 2 3 4 5 do echo ‘‘Ciao $i volte’’ done 56 Esecuzione: $ chmod +x testfor $ ./testfor Ciao 1 volte Ciao 2 volte Ciao 3 volte Ciao 4 volte Ciao 5 volte 57 Esempio In un file di nome mtable: if [$# then echo echo echo echo exit fi -eq 0] ‘‘Errore.’’ ‘‘Sintassi corretta: $0 numero’’ ‘‘Questo programma stampa la tavola pitagorica’’ ‘‘del numero fornito in ingresso’’ 1 n = $1 for i in 1 2 3 4 5 6 7 8 9 10 do echo ‘‘$n * $i = ’expr $i \* $n’ ’’ done 58 Esecuzione: $ chmod 755 mtable $ ./mtable 7 7 * 1 = 7 7 * 2 = 14 ... 7 * 10 = 70 $ ./mtable Errore Sintassi corretta: ./mtable numero Questo programma stampa la tavola pitagorica del numero fornito in ingresso 59 Ciclo for. Tipo 2 Sintassi for ((comando1; expr; comando2)) do comando3 done Significato: • Prima della prima iterazione viene eseguito comando1 (questo comando viene normalmente usato per inizializzare le variabili che controllano il loop). • Viene eseguita la sequenza comando3 ; comando2 finché expr rimane vera (comando2 viene normalmente usato per modificare il valore delle variabili che controllano il loop, in modo da assicurare le terminazione). 60 Esempio In un file di nome esempioFor: for (( i = 0; i <= 5; i++ )) do echo ‘‘Ciao $i volte’’ done Esecuzione: $ chmod +x esempioFor $ ./esempioFor Ciao 1 volte Ciao 2 volte Ciao 3 volte Ciao 4 volte Ciao 5 volte 61 Esempio In un file chiamato nestedFor.sh: for ((i = 1; i <= 5; i++)) do for (( j = 1; j <= 5; j++ )) do echo -n ‘‘$i ’’ done echo ‘‘’’ # Per stampare una linea bianca done Esecuzione: $ $ 1 2 3 4 5 chmod +x nestedFor.sh ./nestedFor.sh 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4 5 5 5 5 62 Esempio. Stampa di una scacchiera sullo schermo In un file chiamato chessboard: for (( i = 1; i <= 9; i++ )) do for ((j = 1; j <= 9; j++ )) do tot = ’expr $i + $j’ tmp = ’expr $tot % 2’ if [$tmp -eq 0] then echo -e -n ‘‘\033[47m else echo -e -n ‘‘\033[40m fi done echo -e -n ‘‘\033[40m’’ # echo ‘‘’’ # done ’’ ’’ per settare il background nero per andare a nuova riga 63 Output: 64 Spiegazione • I cicli sono ripetuti 9 volte perché le scacchiere sono grandi 9 × 9 caselle. Ogni iterazione del ciclo più interno stampa una casella (o bianca o nera); ogni iterazione del ciclo più esterno stampa una riga della scacchiera. • La variabile tmp vale 0 se tot è pari e 1 se tot è dispari. Quindi la variabile tmp viene usata per sapere se deve essere stampata una casella nera o una casella bianca. • Il comando echo -e -n ‘‘\033[47m stampa una casella bianca (il comando -e, che sta per “enable”, permette di leggere e interpretare i caratteri che cominciano con backslash, normalmente usati per i colori). 65 • Il comando echo -e -n ‘‘\033[40m stampa una casella nera. • Utilità del comando: echo -e -n ‘‘\033[40m alla fine del corpo del ciclo più esterno: quando si è scelto un colore, esso viene mantenuto per qualsiasi attività (compresa quella di stampare una linea vuota!). Quindi, una volta settata l’ultima casella al colore bianco, i comandi successivi permetterebbero di stampare una riga vuota, ma essa sarebbe bianca! Inoltre, tutto lo sfondo della shell diventerebbe bianco! (provare a commentare questa riga e vedere che succede). • Il comando echo“ ” stampa una riga vuota. 66 Ciclo while Sintassi: while [condizione] do comando done 67 Esempio In un file chiamato ciclo: if [$# -eq 0] then echo ‘‘Errore: occorre fornire un parametro numerico’’ echo ‘‘Stampa la tavola pitagorica di quel numero’’ exit 1 fi n=$1 i=1 while [$i -le 10] do echo ‘‘$n * $i = ’expr $i \* $n’ ’’ i = ’expr $i + i’ done Esecuzione: $ chmod 755 ciclo $ ./ciclo 7 68 Comando case Quando il test è sul valore di un’unica variable, il comando case è una valida alternativa all’if-then-else multilivello, in quanto risulta più leggibile. Sintassi: case $nome_di_variabile in pattern_1) command_1;; pattern_2) command_2;; ... pattern_N) command_N;; *) command_N+1;; esac 69 Il valore di nome di variabile viene confrontato con i valori pattern 1, pattern 2, ... fino a che non viene trovato un match (uno di questi valori è uguale al valore della variabile). A quel punto, la shell esegue il comando relativo fino al simbolo “;;”. Il comando di default (comando N+1) viene eseguito se non viene trovato nessun match. 70 Esempio In file chiamato car: if [-z $1] # Se il parametro è nullo then rental=’’*** Unknown vehicle ***’’; else rental=$1 fi case $rental in ‘‘car’’) echo ‘‘il veicolo è un’automobile’’ ‘‘van’’) echo ‘‘il veicolo è un caravan’’ ‘‘jeep’’) echo ‘‘il veicolo è una jeep’’ ‘‘bike’’) echo ‘‘il veicolo è una bicicletta’’ *) echo ‘‘Mi dispiace, non conosco questo veicolo...’’ esac 71 Eseguire: $ chmod +x car $ ./car van il veicolo è un caravan $ ./car car il veicolo è un’auto $ ./car mercedes Mi dispiace, non conosco questo veicolo... 72 Come debuggare uno script di shell Esistono due opzioni che permettono di debuggare uno script di shell: • -v: stampa le linee dello script mentre le esegue • -x: ogni comando viene “espanso” e vengono visualizzati i valori delle variabili ed i vari passaggi. La sintassi per usare queste opzioni è: sh opzione <nome dello script> oppure bash opzione <nome dello script> 73 Esempio In un file chiamato somma.sh: tot=’expr $1 + $2’ echo $tot Esecuzione: $ chmod 755 somma.sh $ ./somma 4 5 9 $ sh -x somma 4 5 tot=’expr $1 + $2’ expr $1 + $2 ++ expr 4 +5 + tot = 9 echo $tot + echo 9 9 Dato che -x scrive molte cose, quando lo script è complicato conviene usare -v !! 74 Esecuzione condizionale (&& e ||) && e || sono operatori di controllo. && può essere interpretato come un AND logico. || può essere interpretato come un OR. Funzionamento: comando1 && comando2 comando2 è eseguito se e solo se comando1 restituisce un exit status uguale a zero. (ovvero termina senza errori) comando1 || comando2 comando2 è eseguito se e solo se comando1 restituisce un exit status diverso da zero. (ovvero termina con un errore) 75 Gli operatori && e || possono essere usati insieme: comando1 && comando2 || comando3 Se comando1 restituisce un exit status uguale a zero (ovvero termina senza errori), allora viene eseguito comando2, altrimenti viene eseguito comando3. Esempio $ rm myf && echo ‘‘File myf cancellato con successo’’ || echo ‘‘File myf non trovato’ 76 Funzioni 77 Funzioni Sintassi: nome-funzione() { comando return } Il comando return termina la funzione ed è obbligatorio. 78 Esempio Sulla linea del prompt digitare: $ SayHello() { echo ‘‘Ciao $LOGNAME’’ return } Al momento in cui viene chiusa la parentesi graffa, viene restituito di nuovo il prompt per eseguire il prossimo comando. Per eseguire la funzione, sempre dalla linea del prompt, digitare: $ SayHello Ciao lvannesc 79 Nota: una volta che si spegne il computer e lo si riaccende, la funzione SayHello verrà perduta: essa viene creata solo per la sessione corrente. Se vogliamo che la funzione non venga perduta al momento della terminazione della sessione di lavoro, possiamo aggiungere la funzione al file /etc/bashrc. Ciò può essere fatto solo tramite la password di root. In questo modo la funzione sarà visibile a tutti gli utenti. Alternativamente, la funzione può essere messa nel file .bashrc che si trova nella home directory di un particolare utente, ed allora sarà utilizzabile solo da parte di quell’utente. Una volta fatto questo, per eseguire la funzione occorre uscire e rientrare, oppure, supponendo di essere nella directory che contiene il file .bashrc, digitare: $ source .bashrc (se non siamo nella directory che contiene il file .bashrc, occorre digitare il path completo) 80 Nota Se vogliamo eseguire un’azione al momento del logout, i comandi vanno inseriti nel file .bash logout che si trova nella home directory dei vari utenti. Tipico esempio: si possono aggiungere alla fine del file .bash logout i comandi: echo ‘‘Arrivederci $LOGNAME, premi un tasto per uscire...’’ read .bash logout è l’ultimo file che viene eseguito prima di terminare una sessione (tramite il comando exit). 81 Concetti Avanzati 82 Ancora sui parametri di uno script. Il comando shift Il comando shift serve per spostare di una posizione verso sinistra i parametri di uno script. Ad esempio, se in un determinato istante i parametri si trovano nella posizione $1 = -f, $2 = foo, $3 = bar e si esegue il comando shift, successivamente i parametri si troveranno nelle posizioni: $1 = foo, $2 = bar 83 Per chiarirsi le idee, scrivere il seguente script: echo ‘‘I parametri prima dello shift sono: \$1=$1, \$2=$2, \$3=$3’’ shift echo ‘‘I parametri dopo lo shift sono: \$1=$1, \$2=$2, \$3=$3’’ Se lo script è contenuto in un file che si chiama esempio.sh, eseguirlo nel seguente modo: $ chmod +x esempio.sh $ ./esempio.sh -f foo bar I parametri prima dello shift sono $1=-f, $2=foo, $3=bar I parametri dopo lo shift sono $1=foo, $2=bar, $3= 84 Si possono anche spostare i parametri di più di una posizione, specificando un numero come parametro del comando shift. Ad esempio, il comando shift 2 sposta i parametri di due posizioni (sempre verso sinistra). 85 A cosa serve il comando shift? Serve per parsare i parametri!! Ad esempio, supponiamo di voler scrivere uno script (contenuto in un file di nome dividi) che riceve un numero x (tramite l’opzione -up) e un numero y (tramite l’opzione -down) e calcola x/y. Inoltre supponiamo di voler potere eseguire lo script in entrambi questi modi: $ ./dividi -up 500 -down 5 $ ./dividi -down 5 -up 500 in entrambi questi casi (ovvero indipendentemente dall’ordine dei parametri) lo script deve eseguire la divisione di 500 per 5. L’unico modo è scandire i parametri in cerca delle opzioni -up e -down. 86 Script dividi while [ "$1" ] do if [ "$1" = "-up" ] then num=$2 shift 2 elif [ "$1" = "-down" ] then den=$2 shift 2 else echo "Opzione $1 non riconosciuta" exit 1 fi done echo "Risultato: ‘expr $num / $den‘" 87 Comando getopts Il comando getopts serve per gestire le opzioni che vengono passate a uno script. Sintassi: getopts <stringa-di-opzioni> <nome-di-variabile> <stringa-di-opzioni> contiene le lettere che identificano le opzioni che devono essere riconosciute. Se una di queste lettere è seguita dal simbolo “:”, ci si aspetta che l’opzione abbia un argomento, che deve essere separato dalla lettera tramite un carattere di spazio bianco. 88 Ogni volta che è invocato, getopts mette la prossima opzione nella variabile di shell <nome-di-variabile>. Quando un’opzione richiede un argomento, getopts memorizza l’argomento nella variabile OPTARG. Se viene data un’opzione tramite una lettera che non rientra in stringa-di-opzioni>, getopts memorizza il carattere ? in <nome-di-variabile>. 89 Esempio Il seguente script stampa gli argomenti di ogni opzione. Supponiamo che esso sia memorizzato in un file di nome esempio, il suo funzionamento è: $ ./esempio Argomento Argomento Argomento Argomento -a opzione1 -b opzione2 -c opzione3 -d opzione4 dell’opzione a: opzione1 dell’opzione b: opzione2 dell’opzione c: opzione3 dell’opzione d: opzione4 90 while getopts a:b:c:d: do case "$opt" in a) b) c) d) *) esac done echo echo echo echo "Argomento "Argomento "Argomento "Argomento opt oa="$OPTARG";; ob="$OPTARG";; oc="$OPTARG";; od="$OPTARG";; echo "errore";; dell’opzione dell’opzione dell’opzione dell’opzione a: b: c: d: $oa" $ob" $oc" $od" 91 Selezionare porzioni di un file tramite l’utility cut Supponiamo di avere, all’interno di un file di testo chiamato studenti le due seguenti colonne, separate dal carattere TAB: 1 2 3 4 Rossi Bianchi Verdi Gialli Tramite il comando cut usato con l’opzione -f posso selezionare e stampare a video una colonna: $ cut -f1 studenti 1 2 3 4 92 Analogamente, posso selezionare e stampare la seconda colonna come segue: $ cut -f2 studenti Rossi Bianchi Verdi Gialli 93 Concatenare le righe di un file tramite l’utility paste Supponiamo di avere i due files seguenti: il file studenti che contiene: 1 2 3 4 Rossi Bianchi Verdi Gialli ed il file voti che contiene: 1 2 3 4 30 21 27 24 94 Tramite il seguente comando si ottiene il seguente risultato: $ paste studenti voti 1 Rossi 1 30 2 Bianchi 2 21 3 Verdi 3 27 4 Gialli 4 24 95 Si può mettere insieme l’effetto del comando cut con quello del comando paste per ottenere risultati più sofisticati: $ cut -f2 studenti > tmp1 $ cut -f2 voti > tmp2 $ paste tmp1 tmp2 Il risultato è: Rossi Bianchi Verdi Gialli 30 21 27 24 96 L’utility join Adesso scriviamo il seguente comando e otteniamo il seguente risultato: $ join studenti 1 Rossi 2 Bianchi 3 Verdi 4 Gialli voti 30 21 27 24 join cerca gli elementi della prima colonna di un file e li matcha con quelli della prima colonna di un altro file (senza ripeterli nel risultato). Funziona solo se ci sono dei campi comuni in entrambi i files e se sono identici. 97 L’utility tr Il comando seguente comando restituisce il seguente risultato: $ tr ‘‘s2’’ ‘‘3x’’ < studenti 1 Ro33i 2 Bianchi x Verdi 4 Gialli Ogni occorrenza del carattere“s”è stata sostituita col carattere“3”, ogni occorrenza del carattere “2”è stata sostituita col carattere “x”. 98 L’utility tr può lavorare anche sui range. Scrivere sulla riga del prompt: $ tr ‘‘[a-z]’’ ciao a tutti CIAO A TUTTI che magia! CHE MAGIA! ‘‘[A-Z]’’ (per uscire fare control-C). 99 Manipolare i file con l’utility awk Si consideri un file chiamato spesa che contiene: prosciutto torta minestrone formaggio frutta stagionato fresco surgelato fresco fresco 4 10 4 12 5 Il seguente comando da il seguente risultato: $ awk ’/fresco/ {print $3}’ spesa 10 12 5 Seleziona ogni riga che contiene la parola fresco e stampa il terzo campo. 100 Find and replace tramite l’utility sed Si consideri un file chiamato frase contenente il seguente testo: Il vino italiano è buono. La birra tedesca è buona. La birra è meglio del limoncello. Il seguente comando restituisce il seguente risultato: $ sed ’/birra/s//grappa/g’ frase Il vino italiano è buono. La grappa tedesca è buona. La grappa è meglio del limoncello. /birra/ significa “trova la parola birra” s//grappa significa “rimpiazzala con la parola grappa” g significa che l’operazione deve essere effettuata globalmente (su tutto il file). 101 Eliminare le righe ripetute tramite l’utility uniq Dato il file (di nome frase): Ciao a tutti 1234 1234 Il comando seguente restituisce il seguente risultato: $ uniq frase Ciao a tutti 1234 Per ordinare un file e poi eliminare le righe ripetute, si può fare: $ sort <nome-file> | uniq 102 Selezionare righe con una parola tramite l’utility grep Dato il file (di nome frase): Ricordati di studiare altrimenti sono guai è difficile capire ma ci riuscirai Il comando seguente restituisce il seguente risultato: $ grep ‘‘di’’ frase Ricordati di studiare è difficile capire Sono state restituite le righe in cui compare la stringa“di”anche come parte di una parola. 103 Esercizi 104 Esercizio 1 Scrivere uno script che riceve in ingresso due numeri (digitati dall’utente) e ne stampa a video la somma. 105 Svolgimento 1 if [$# -ne 2] then echo ‘‘Questo script si usa cosi:’’ echo ‘‘$0 x y’’ echo ‘‘dove x e y sono due numeri da sommare’’ exit 1 fi echo ‘‘La somma di $1 piu $2 è uguale a ’expr $1+$2’ ‘‘ Se il file si chiama somma: $ chmod 755 somma $ somma Questo script si usa cosi: somma x y dove x e y sono due numeri da sommare $ somma 10 5 La somma di 10 piu 5 è uguale a 15 106 Esercizio 2 Scrivere uno script che consenta di eseguire le operazioni aritmetiche di: • somma • sottrazione • moltiplicazione • divisione Se il nome del file che contiene lo script è operazioni, lo script deve funzionare cosı̀: $ ./operazioni 20x3 Il risultato è 60 107 Svolgimento 2 if test $# = 3 then case $2 in +) let z=$1+$3;; -) let z=$1-$3;; /) let z=$1/$3;; x|X) let z=$1*$3;; *) echo ‘‘Attenzione: $2 è un operatore non valido.’’ echo ‘‘solo +,-,x,/ sono operatori validi.’’ exit 1;; esac echo Il risultato è $z else echo ‘‘Usage: $0 valore1 op valore2’’ echo ‘‘Dove valore1 e valore2 sono numeri’’ echo ‘‘e op è uno tyra i simboli +,-,x,/’’ fi 108 Esercizio 3 Scrivere uno script che permetta di visualizzare a video la data, l’ora, il nome dell’utente e la directory corrente. 109 Svolgimento 3 echo echo echo echo ‘‘Hello $LOGNAME’’ ‘‘Current date is ’date’ ‘‘ ‘‘User is ’whoami’ ‘‘ ‘‘Current directory is ’pwd’ ‘‘ 110 Esercizio 4 Scrivere uno script che consenta di stampare a video i numeri in ordine inverso. Ad esempio, se il numero è 123, lo script deve visualizzare 321. 111 Svolgimento 4 if [$# -ne 1] then echo ‘‘Usage: $0 n’’ echo ‘‘lo script stampa il reverse del numero n’’ exit 1 fi n=$1 rev=0 sd=0 while [$n -gt 0] do sd=’expr $n % 10’ rev=’expr $rev \* 10 + $sd’ n=’expr $n / 10’ done echo ‘‘Il reverse del numero è: $rev’’ 112 Esercizio 5 Scrivere uno script che riceve in ingresso un numero e stampa a video la somma delle sue cifre. Ad esempio, se il numero è 123, lo script deve visualizzare 1+2+3=6. 113 Svolgimento 5 if [$# -ne 1] then echo ‘‘Usage: $0 n’’ echo ‘‘Lo script stampa la somma delle cifre del numero n’’ exit 1 fi n=$1 sum=0 sd=0 while [$n -gt 0] do sd=’expr $n % 10’ sum=’expr $sum + $sd’ n=’expr $n / 10’ done echo ‘‘La somma delle cifre è $sum’’ 114 Esercizio 6 Scrivere uno script che controlla se nella directory corrente c’è un file il cui nome è uguale alla stringa che ha ricevuto in ingresso. 115 Svolgimento 6 if [$# -ne 1] then echo ‘‘Usage: $0 file-name’’ echo ‘‘controlla l’esistenza di un file di nome file-name’’ exit 1 fi if [ -f $1 ] then echo ‘‘il file $1 esiste!’’ else echo ‘‘il file $1 non esiste’’ fi 116 Esercizio 7 Scrivere uno script che riceve una stringa e controlla se questa stringa contiene o meno il simbolo “*”. Se lo contiene, stampare a video il messaggio “tutto ok”, altrimenti stampare a video il messaggio “devi aggiungere il simbolo *”. Esempio: Supponiamo che il file che contiene lo script si chiami esercizio: $ ./esercizio /bin devi aggiungere il simbolo * $ ./esercizio /bin/* tutto ok Attenzione: Risolvere l’esercizio senza usare cicli ! 117 Svolgimento 7 if [$# -ne 1] then echo ‘‘Usage: $0 s’’ echo ‘‘controlla se la stringa s contiene il carattere *’’ exit 1 fi cat ‘‘$1’’ > /tmp/file.$$ 2> /tmp/file0.$$ grep ‘‘*’’ > /tmp/file.$$ /tmp/file0.$$ if [$? -eq 1] then echo ‘‘devi aggiungere il simbolo *’’ else echo ‘‘tutto ok’’ fi rm -f /tmp/file.$$ rm -f /tmp/file0.$$ 118 Esercizio 8 Scrivere uno script che stampi a video il contenuto di un file a partire da una certa riga x e per un certo numero di righe n. Il nome del file, la riga di partenza x ed il numero di righe da stampare n sono passati come argomento allo script. Esempio Supponiamo il file che contiene lo script si chiami esercizio e che il file myf abbia il seguente contenuto: Ciao, oggi facciamo esercizi di Sistemi Operativi. Spero che sia divertente e che serva ad imparare il linguaggio di scripting Allora: $ ./esercizio 4 2 myf Spero che sia divertente e che serva ad imparare Attenzione: Risolvere l’esercizio senza usare cicli ! 119 Svolgimento 8 if [$# -ne 3] then echo ‘‘Usage: $0 x n nome-file’’ echo ‘‘stampa n righe del file nome-file a partire dalla riga x-esima’’ exit 1 fi if [-e $3] then tail +$1 $3 | head -n$2 else echo ‘‘Il file $3 non esiste’’ fi 120 Esercizio 9 Scrivere uno script che: • Se riceve l’opzione -c esegue il clear dello schermo. • Se riceve l’opzione -d mostra la lista dei file che sono presenti nella directory corrente. • Se riceve l’opzione -m mette in esecuzione mc (midnight commander shell), se è installato. • Se riceve l’opzione -e <nome-editor> apre l’editore <nome-editor>, se è installato. 121 Svolgimento 9 # # # Funzione per pulire lo schermo cls() { clear echo ‘‘Lo schermo è stato pulito. Premi un tasto per continuare...’’ read return } 122 # # # Funzione che visualizza i file presenti nella directory corrente show_files() { ls echo ‘‘La lista dei file è stata visualizzata. Premi un tasto per continuare...’’ read return } 123 # # # Funzione che fa partire il midnight commander shell start_mc() { if which mc > /dev/null then mc echo ‘‘Midnight commander shell eseguita. Premi un tasto per continuare...’’ read else echo ‘‘mc non installato! Premi un tasto per continuare...’’ read fi return } 124 # # # Funzione che mette in esecuzione un editore testuale start_ed() { ced=$1 if which $ced > /dev/null then $ced echo ‘‘$ced messo in esecuzione. Premi un tasto per continuare...’’ read else echo ‘‘$ced non installato. Premi un tasto per continuare...’’ read fi return } 125 # # # Funzione che stampa l’help del nostro script print_help() { echo ‘‘Usage: $0 <opzione>’’ echo ‘‘Possibili opzioni:’’ echo ‘‘-c: pulisce lo schermo’’ echo ‘‘-d: mostra i files presenti nella directory corrente’’ echo ‘‘-m: mette in esecuzione il midnight commander shell’’ echo ‘‘-e <nome-editor>: mette in esecuzione l’editore <nome-editor>’’ return } 126 # # # Programma Principale if [$# -eq 0] then print_help exit 1 fi while getops cdme: opt do case $opt in c) cls;; d) show_files;; m) start mc;; e) nomeEditor=’’$OPTARG’’; start_ed $nomeEditor ;; \?) print_help; exit 1;; esac done 127 Esercizio 10 Scrivere uno script che: • Controlla se il parametro che gli viene passato è un file, una directory, oppure non esiste e stampa questa informazione a video. Poi scrivere uno script che: • Stampa a video solo i nomi delle directory contenute nella directory corrente. • Stampa a video solo i nomi dei file contenuti nella directory corrente. • Stampa a video il numero di file e di directory contenuti nella directory corrente. 128 Svolgimento 10 - Prima parte if [ -z \$1 ] then echo ‘‘Utilizzo: $0 nome di un file’’ exit 0 fi if [ -f $1 ] then echo ‘‘$1 è un file’’ exit 0 fi if [ -d $1 ] then echo ‘‘$1 è una directory’’ exit 0 fi 129 Svolgimento 10 - Seconda parte # # # Restituisce la lista di file j=0 for i in ‘ls‘ do if [ -f $i ] then echo $i j=‘expr $j + 1‘ fi done echo ‘‘Ci sono $j file nella directory corrente’’ 130 # # # Restituisce la lista di directory j=0 for i in ‘ls‘ do if [ -d $i ] then echo $i j=‘expr $j + 1‘ fi done echo ‘‘Ci sono $j directory nella directory corrente’’ 131 Esercizio 11 Supponiamo di avere a disposizione un file chiamato fileDiDati che contiene il seguente testo: primo 3 secondo 10 terzo 25 quarto 2 quinto 12 Scrivere uno script che permette di stampare a video soltanto le linee per le quali il valore numerico (che appare sulla seconda colonna) è superiore a 10. 132 Svolgimento 11 cat fileDiDati | while true do read ligne if [ "$ligne" == "" ] then exit 0 fi set -- $ligne if [ $2 -ge 10 ] then echo -e "$1 \t $2" fi done 133 Esercizio 12 Si consideri un file di testo chiamato addizioni nel quale ogni linea contiene due interi. Per esempio 2 7 3 13 1 28 ... Scrivere uno script chiamato somma che usa il file addizioni e produce il seguente risultato: $ ./somma < addizioni 2 + 7 = 9 3 + 13 = 16 1 + 28 = 29 ... 134 Svolgimento 12 while read n1 n2 do echo ‘‘${n1} + ${n2} = ‘expr ${n1} + ${n2}‘’’ done 135 Esercizio 13 Sulla base dell’esercizio precedente, scrivere uno script somma che usa il file addizioni e produce il seguente risultato: $ ./somma addizioni 2 + 7 = 9 3 + 13 = 16 1 + 28 = 29 ... 136 Svolgimento 13 while read n1 n2 do echo ‘‘${n1} + ${n2} = ‘expr ${n1} + ${n2}‘’’ done < $1 137 Esercizio 14 Scrivere uno script che rinomina tutti i files con estensione .tex che sono presenti nella directory corrente, facendo diventare la loro estensione .tex.old. 138 Svolgimento 14 for i in *.tex do mv $i $i.old done 139 Esercizio 15 Scrivere uno script che rinomina tutti i files con estensione .tex che sono presenti nella directory corrente, facendo diventare la loro estensione .TEX. 140 Svolgimento 15 for i in *.tex do mv $i ‘basename $i .tex‘ .TEX done 141 Esercizio 16 Scrivere uno script che stampa a video la lista di tutti i file .html che sono presenti nella directory corrente e scrive la prima linea di ogni file HTML in un file chiamato File Heads 142 Svolgimento 15 echo ‘‘Lista dei files:’’ ls -lA FILE_LIST=’’‘ls *.html‘’’ echo FILE_LIST: ${FILE_LIST} RESULT=’’’’ for file in ${FILE_LIST} do FIRST_LINE=‘head -2 ${file}‘ RESULT=${RESULT}${FIRST_LINE} done echo ${RESULT} | cat >FILE_HEADS echo ‘‘ ’$RESULT’ scritto.Tutto ok.’’ 143