Guida di base alla programmazione della shell Bash
Transcript
Guida di base alla programmazione della shell Bash
Guida di base alla programmazione della shell Bash Introduzione L'intento di questa guida che sarà pubblicata a puntate è quello di esporre nel modo più chiaro possibile le basi della programmazione della shell Bash utilizzando le mie competenze in merito e numerosi documenti trovati e letti in rete. Tengo a precisare che questo non vuole essere un corso di "fondamenti della programmazione" è dunque consigliata la lettura a chi già possiede come bagaglio culturale i rudimenti della programmazione anche se ciò non significa che sconsiglio la lettura a chi non li possiede. Detto ciò prima di tutto va detto cosa è una shell e il ruolo che ricopre nei sistemi unix like più che in tutti gli altri sistemi. La shell di un sistema operativo è un'astrazione che permette all'utente di interagire con la macchina; essa si pone tra il kernel e l'utente ed è a tutti gli effetti un processo allo stesso livello di quello utente; ciò vuol dire che possono coesistere contemporaneamente più shell. Generalmente essa è di tipo testuale, ma esistono delle anche implementazioni grafiche (le GUI, acronimo di Graphical User Interface). Nei sistemi unix like, le shell hanno da sempre ricoperto un ruolo molto importante, i puristi la preferiscono ancora alle GUI e non a caso, ma per la loro flessibilità ed alta configurabilità. Abbiamo detto che la shell si pone tra il kernel e l'utente per facilitare il dialogo con la macchina, ciò vuol dire che dispone di una serie di "costrutti" e "costani" che richiamate permettono di svolgere delle operazioni. Questi costruti e queste costanti possono essere richiamate da riga di comando, ed è ciò che facciamo più spesso quando chiediamo alla shell il contenuto di una directory con il comando ls, quando creiamo un file o lo modifichiamo o lo muoviamo ecc... .Tutte queste operazioni possono anche essere elencate in un normale file di testo ed eseguite successivamente in maniaera sequenziale; ciò rappresenta uno script. Ora per capirci un linguaggio script è un linguaggio che lascia "precisare" ad un interprete le istruzioni contenute in un file; tali linguaggi sono generalmente più lenti di quelli compilati nell'esecuzione proprio perchè l'interprete deve processare le istruzioni ma hanno dalla loro il fatto che sono generalmente molo semplici e flessibili. Tornando a noi diciamo subito che la bash (Bourne Again SHell) è la shell più diffusa per GNU/Linux ed offre diversi costrutti che consentono ai programmatori di realizzare degli script anche piuttosto complessi. Con questo chiudo l'introduzione e passiamo al primo capitolo. CAPITOLO I I file di configurazione Per configurare la shell si possono usare cinque files di configurazione,(anche se spesso nelle varie distribuzioni non vengono usati tutti vedremo che non e' difficile crearsene di propri). I file sono: /etc/profile /etc/bashrc ~/.bash_profile ~/.bashrc ~/.bash_logout Possiamo dividerli in due gruppi: file globali; quelli che contengono direttive di configurazione valide per tutti gli utenti, sono quelli che si trovano sotto la directory /etc: /etc/profile è il file di configurazione globale che determina le variabili di ambiente e i programmi da eseguire per ogni utente che manda in esecuzione la shell. Vi è poi: /etc/bashrc è un file di configurazione globale molto simile al precedente,(per questo motivo spesso non e' usato), contiene gli alias (collegamenti brevi a comandi molto lunghi) e pezzi di codice nel linguaggio di scripting della shell.Tutto puo' essere spostato senza problema nel file /etc/profile. file locali; contengono direttive di configurazione valide solo per l'utente che possiede la cartella nella directory /home che li contiene. Non a caso nell'elenco sono preceduti da un carattere tilde (~ che nei sistemi Unix e' un collegamento breve alla directory base dell'utente), e preceduti da un punto che li rende "invisibili" al comando ls senza l'argomento -a: ~/.bash_profile è un file di configurazione locale che contiene direttive di configurazione e variabili di ambiente specifiche dell'utente al quale appartiene la directory /home in cui si trova. Il file viene letto ed eseguito successivamente a quelli globali e' modifica o sovrascrive variabili che riguardano esclusivamente l'utente.Per concludere: ~/.bashrc è un file di configurazione locale che contiene direttive di configurazione come gli alias, o funzioni definite dall'utente. Il file viene letto ed eseguito successivamente a quelli globali, gli alias o le funzioni saranno specifici dell'utente e non influenzeranno nessun altro utente; è il corrispondente locale di /etc/bashrc; poi: ~/bash_logout è un file di configurazione locale che contiene comandi da eseguire quando l'utente esce dalla shell. I comandi influenzano solo l'utente che possiede la cartella /home nella quale si trova. CAPITOLO II Le variabili d'ambiente Vediamo ora una serie di variabili d'ambiente che ci saranno utili per i nopstri script. Queste variabili andranno configurate andando a modificare i file di configurazione nominati nel precedente capitolo. La prima variabile da configurare è la variabile PATH che definisce le directory a cui possiamo accedere da qualsiasi punto del filesystem. Questo è molto utile in quanto per esempio potremmo avere un programma nella directory /bin chiamato "programma", se la directory /bin è presente nella variabile PATH possiamo lanciare il programma da qualsiasi posizione nel filesystem semplicemente digitando il suo nome (senza dimenticare gli attributi di esecuzione e la forma ./ prima del nome tipica dei sistemi unix), altrimenti saremmo costretti o a digitare l'intero percorso /bin/programma oppure portarci nella directory e successivamente lanciare il programma. La variabile PATH è definita in /etc/profile e ha una sintassi leggermente diversa dal normale, ogni percorso di directory alla destra dell'operatore di assegnazione "=" e separata dal carattere ":" ad esempio esempio: PATH=/bin:/usr/bin:/usr/local/bin export PATH renderà possibile mandare in esecuzione da qualsiasi punto del filesystem tutti gli eseguibili che si trovano nelle cartelle dichiarate nella variabile PATH. Se dovesse capitare di voler aggiungere altri percorsi alla variabile PATH potremmo digitare semplicemente o da prompt: PATH=$PATH:/usr/games export PATH oppure nel nostro script; queste istruzioni aggiungeranno ai percorsi già esistente della variabile PATH il percorso che si trova alla destra dei due punti perchè il nome di una variabile preceduto dal simbolo "$" verrà espanso con il contenuto originale della variabile ovvero: /bin:/usr/bin:/usr/local/bin diventerà, poi gli verrà aggiunto il :/usr/games modificandola in: /bin:/usr/bin:/usr/local/bin:/usr/games. Si può modificare il proprio path inserendo le stesse righe in ~/.bash_profile; ricordo questo perchè nel Capitolo I avevamo detto che aluni dei file di configurazione potevano non essere presenti in alcune distro perchè il loro contenuto poteva essere "riversato" in altri file, detto ciò se si verifica uno di questi casi sapete dove andare a mettere tali righe. Proseguendo la nostra carrellata tra le variabili d'ambiente e parliamo del file "storico"; mi spiego, tutti abbiamo sicuramente fatto caso al fatto che una volta utilizzato da prompt un comando la shell è in grado di ricordare i comandi immessi dall'utente. Questo perchè vengono salvati nel file: ~/.bash_history e possono essere richiamati premendo i tasti freccia in su e freccia in giù, questo "comportamento" può essere modificato configurando le variabili: HISTSIZE HISTFILE HISTFILESIZE E' da ricordare che oramai per consuetudine le variabili di ambiente vengono indicate usando lettere maiuscole a differenza dele locali per cui si usani le minuscole. Queste variabili sono definite in /etc/profile ma possono essere anche scritte in ~/.bash_profile. Vediamo ora cosa indicano: la variabile HISTSIZE indica il numero massimo dei comandi da memorizzare nel file storico; normalmente il valore preimpostato è 500; la variabile HISTFILE indica il file che deve essere usato per contenere i comandi digitati; normalmente il file preimpostato è ~/.bash_history, puo' anche non essere impostato, e il "file" storico (se così lo si può chiamare in queso caso) si limitera' alla sessione di lavoro corrente; la variabile HISTFILESIZE indica la grandezza fisica massima che puo' avere il file storico. Vediamo ora le variabili che influenzano le funzioni di posta; esse sono: MAIL MAILCHECK MAILPATH MAIL_WARNING queste variabili sono normalmente definite nel file /etc/profile ma possono anche essere scritte nel file ~/.bash_profile . Esaminiamole: la variabile MAIL indica il file che viene scritto quando arriva una mail al sistema. Normalmente contiene la stringa /var/spool/mail/nome_utente . Si può così indicare alla shell il file da controllare per l'arrivo di nuovi messaggi; la variabile MAILCHECK indica il tempo in minuti che deve trascorrere tra un controllo di posta e il successivo. Il valore preimpostato è 60 (quindi ogni minuto); la variabile MAILPATH indica il percorso per raggiungere le directory di posta. Normalmente contiene il valore /var/spool/mail . Puo' essere usata anche per personalizzare il messaggio che notifica l'arrivo della nuova posta, ad esempio esempio: MAILPATH='/var/spool/mail/nome_utente "Hai posta....!"' la variabile MAIL_WARNING se definita, la shell vi informera' con un messaggio, se state leggendo un messaggio gia' letto in precedenza. Vediamo ora la variabile TMOUT; essa specifica quanto tempo la shell attenderà per l'inserimento del nome utente o della password al login, ad esempio: TMOUT=60 attendera' un minuto prima di reinizializzare il login. Vi sono poi delle variabili d'ambiente riguardanti il promp dei comandi. Il prompt rappresenta il sistema che è in attesa di un input, generalmente per la shell Bash è del tipo: nome_utente@nome_computer:[#,$] dove se compare il simbolo # dopo i : significa che si hanno i diritti di root altrimenti $ indica che si hanno semplicemente i diritti di un utente.Qualche riga prima ho scritto "generalmente" perche' quello che vediamo sullo schermo sono i valori delle variabili d'ambiente PS1 e PS2 definite nel file /etc/profile che e' di proprieta' dell'utente root. Se vogliamo un prompt diverso potremmo ridefinire la variabile PS1 nel file ~/.bash_profile. Questa variabile accetta dei valori predefiniti che sono ottenuti facendo seguire dal backslash dei caratteri speciali forniti di seguito: \t \d \n \s \w \W \u \h \# \! \$ l'ora corrente nel formato HH:MM:SS la data in formato esteso es. "Tue May 18" un carattere di nuova linea il nome della shell la directory corrente il percorso completo alla directory corrente il nome dell'utente il nome della macchina il numero del processo associato al comando in esecuzione la posizione numerica nel file storico dei comandi se l'UID e' 0 mostra un "#", altrimenti un "$" Un esempio di assegnazione di tale variabile è: PS1="[\u@\h \W]\$" che produce un prompt : [root@macchina1 /root]# oppure: PS1="[\t \s]\$ " che produce un prompt: [12:18:24 bash]# oltre ai caratteri prima elencate, la variabile PS1 puo' contenere anche comandi per esempio se volessi far apparire la versione del kernel: PS1="`uname -r` \$ " il prompt sarà ad esempio: 2.4.17# La variable PS2 determina invece l'aspetto del prompt secondario e viene visualizzato quando si digitano comandi incompleti. Questo succede quando abbiamo a che fare con comandi molto lunghi, possiamo digitare il comando su piu' linee facendo precedere il comando invio da un backslash, a quel punto la shell capira' che non abbiamo terminato e ci presentera' il prompt secondario che significa che sta attendendo successivi comandi. Se vogliamo vedere come si presenta il prompt secondario nella nostra shell possiamo digitare: echo $PS2 ed ottenere il valore della variabile; il significato di echo verrà trattato nella guida. Tutto quello che e' stato detto per PS1 vale anche per PS2. CAPIOLO III Struttura dello script La programmazione della shell Bash avviene tramite la stesura di script ovvero, la stesura di un file di testo con estensione .sh ,(ma può anche non avere esensione), contenente le istruzioni che vengono poi eseguite dall'interprete. Per eseguire tale script quindi bisogna avviare il programma interprete e informarlo quale script questo deve eseguire. Nei sistemi Unix esiste una stringa attraverso la quale si automatizza l'esecuzione dei file script. Se ipotizziamo di avere un file di nome "my" che contiene delle istruzioni con il "comando": $ bash my sarà eseguito. E' stato eseguito l'interprete "bash" che ha interpretato lo script "my". Si può evitare questo passaggio dichiarando all'inizio del file script il programma che deve occuparsi di interpretarlo. La sintassi da usare è: #! persorso&nome dell'interprete questo vale in genere per tutti i linguaggi script (vale ad esempio anche per il linguaggio Tcl/Tk molto usato nei sistemi unix), nel nostro caso di programmazione della bash la riga sarà: #!/bin/bash Voglio ora dire una cosa molto importante, è buona norma non provare i propri script come utente root per evitare "danni" a file di sistema almeno finchè non si è raggiunti una certa esperienza o se lo script lo richiede per le sue operazioni. Le variabili Per "conservare" temporeneamente uno o più valori derivati magari da calcoli o altro vengono generalmente usate nella programmazione le variabili, ovvero si usano delle aree di memoria del computer assegnandogli un nome a cui si possa far riferimento in seguito per recuperare il valore assegnato, si può pensare a una variabile come una serie di celle di memoria contigue. Abbiamo già incontrato delle particolari variabili, quelle d'ambiente create autonomamente dal sistema operativo e definite in file e non in aree di memoria come detto sopra. Vediamo ora come si definiscono all'interno di uno script. La definizione di una variabile avviene nel seguene modo: nome_variabile=valore Avviene dunque per mezzo dell'operatore di assegnazione "=". Fissato il valore di una variabile per utilizzarlo non dobbiamo fare altro che far precedere il suo nome dal simbolo "$" ad esempio per conoscere il valore della variabile PHAT digiteremo: echo $PHAT che stamperà sul video ad esempio: /bin:/usr/bin:/usr/local/bin vedremo in seguito cosa esprime l'operatore echo ma possiamo anticipare che esso permette di stampare sullo schermo. Puntualizziamo ora una cosa; generalmente nella maggior parte dei linguaggi quando si definisce una variabile si definisce anche il tipo ,(variabili tipizzate), che rappresenta la natura del dato che la sessa deve contenere, in molti linguaggi script e la programmazione della shell bash non fa eccezione, ciò non viene fatto e una variabile può contenere un valore numerico o una stringa dichiarando la variabile nello stesso modo, ad esempio: #!/bin/bash x=12 y="a" z="Gnu/Linux" come possiamo vedere la differenza non sta nella dichiarazione ma nell'assegnazione del valore, per le stringhe utilizziamo infatti i doppi apici per assegnare il contenuto.Vi è poi un operatore molto utile che permette di rendere disponibile ai programmi che l'utente usa con il comando export nel seguente modo: export nome_variabile Quano visto indica le la dichiarazione e l'utilizzo delle variabili cosiddette scalari o semplici ci sono poi le variabili vettoriali chiamati array. Nella shell come accade nel C gli array sono "zero based" ovvero il primo elemento e quello di indice zero e inoltre non è necessario stabilire la dimensione basta assegnare un valore in una posizione qualunque e l'array viene creato, ad esempio: data[2]="Free Software Foundation" l'array "data" possiede come terzo elemento ,(perchè è zero based), la stringa "Free Software Foundation".In questo modo è stato creata una variabile vettoriale che ha le prime due posizioni vuote e la terza contenente una stringa. E' possibile assegnare tutti i valori degli elementi di un array con una sola riga nel seguente modo: data=(val1 val2 ... valN) i valori "val.." possono essere espressi nella forma: [indice]=valore è ovvero possibile sia indicare esplicitamente l'indice dell'elemento da assegnare, sia farne a meno e quindi lasciare che sia semplicemente la posizione dei valori a stabilire l'elemento rispettivo che dovrà contenerli. Vediamo ora come fare riferimento al contenuto di una cella di un array: $data[indice] se si legge un array come se fosse una variabile scalare, ad esempio $data si ottiene il contenuto del primo elemento (quello con indice zero) ed è del tutto uguale a scrivere: $data[0] E' possibile riferirsi agli elementi di un array tutti contemporaneamente; per fare questo si utilizza il simbolo "*", oppure "@", al posto dell'indice; per conoscere poi la dimensione di un array si utilizzano indistintamente: $#data[*] oppure: $#array[@] tenendo presente che l'istruzione è #data[*] o #array[@] e che l'$ indica i privilegi che ha l'utente corrente. CAPITOLO IV Input&Ouput Abbiamo già incontrato il comando echo "..." ed abbiamo anticipato che permette di stampare sullo schermo stringhe, numeri e valori di variabili in genere; vediamo ora come leggere i dati immessi dall'utente ed approfondiremo il comando echo. Per leggere da tastiera useremo il comando "read" che è un comando interno della shell che copierà l'input dell'utente in una variabile, ad esempio: #!/bin/bash echo -n "inserisci il nome di Torvalds: " read nome echo "per te il nome di Torvalds è: $parola" l'opzione -n di echo comunica al programma di non andare a capo dopo l'ultima parola perchè in questo caso l'input dell'utente verrà visualizzato subito dopo la fine della stringa. Il comando "read" apetterà che l'utente immetta qualcosa e prema invio e aspetterà all'infinito che venga premuto il tasto invio. Se l'utente non immette nulla e preme invio read sara' eseguito comunque e il programma continuerà con l'istruzione successiva. Strutture di controllo Le strutture di controllo ci permettono di prendere delle "decisioni" e gestire eventuali errori nel nostro script. Analizziamo un esempio: #!/bin/bash cp /etc/my echo "Copiato." il programma copierebbe il file /etc/my nella directory corrente e stamperebbe a video la stringa "Copiato."a condizione, che esista un file chiamato my nella directory /etc, altrimenti otterremmo dalla shell un errore, sarebbe meglio se il programma controllasse l'esistenza di /etc/my prima di provare a copiarlo. Per controllare se esise il file my nella directory e operare di conseguenza dobbiamo utilizzare il costrutto if. Il cosrutto if che presenta varie forme, (if..else..elif..fi), ci permette di modificare il flusso del programma al verificarsi di una condizione. Assieme al costrutto if per questo specifico caso dobbiamo usare il "comando" test che ci permetterà di sapere se è presente il file, il tutto sarà riscritto nel seguente modo: #!/bin/bash if test -f /etc/my then cp /etc/my . echo "Copiato." else echo "il file non esiste" exit fi dunque se il file esiste copiamo il file stesso e stampiamo sullo schermo la scritta "Copiato." altrimenti (else) si stampa sullo schermo "il file non esiste". La parola chiave test che controlla se il file esiste, puo essere sostituita dalle parentesi quadre, facendo attenzione a lasciare uno spazio prima degli argomenti. Notiamo il paramero "-f", vi propongo una breve lista di parameri e relativo significato: -d -e -f -g -r -s -u -w controlla controlla controlla controlla controlla controlla controlla controlla se se se se se se il se il file e' una directory il file esiste il file e' un file regolare il file ha il bit SGID settato il file e' leggibile dall'utente che esegue lo script la dimensione del file non e' 0 file ha il bit SUID settato il file e' scrivibile la riga diventa: if [ -f /etc/my ]; then .... notiamo il ; per indicare che l'istruione è terminata. Abbiamo poi il costrutto "else" che è usato per indicare al programma l'azione da compiere se la condizione non e' soddisfatta. C'e' anche il costrutto "elif" che e' la somma dei costrutti "else" e "if" che permette di evitare un susseguirsi di "if" quando si vogliono testare piu' condizioni. Se si vuole testare una variabile e' buona norma includerla dentro doppie virgolette, affinche' venga "espansa" correttamente ad esempio in questo modo: if [ "$mia_variabile"=5 ]; then .... CAPITOLO V Costrutti condizionali Il costrutto Case La struttura di tale cosruto è: case .. in ......... ......... esac esso è molto simile al costrutto "if" e viene usato per verificare molte condizioni su di una stessa variabile senza usare ulteriori variabili per ogni condizione. Ad esempio: #!/bin/bash x=5 case $x in 0) echo "Il valore di x: 0." ;; 5) echo "Il valore di x: 5." ;; 9) echo "Il valore di x: 9." ;; *) echo "Valore sconosciuto" esac il costrutto testera' il valore di x cont tre valori noti, prima controllera' se il valore e' 0, successivamente se e' 5, al termine se il valore e' 9. Se nessuna delle condizioni si avverera' verra' eseguita la il blocco di codice della condizione "*" che sarà espansa come "qualsiasi valore". Ogni blocco di codice che segue una condizione deve essere terminato con la sequenza di caratteri ";;". L'utilià di questo costrutto si evince se cerchiamo di svolgere le stesse operazioni utilizzando il costrutto if: #!/bin/bash x=5 if [ "$x" -eq 0 ]; then echo "Il valore di x: 0." elif [ "$x" -eq 5 ]; then echo "Il valore di x: 5." elif [ "$x" -eq 9 ]; then echo "Il valore di x: 9." else echo "Valore sconosciuto" fi il tutto è sicuramente meno leggibile e include più istruzioni rendendo quindi il codice più "pesante" nell'esecuzione. Il costrutto Select Il costrutto "select" permette all'utente di effettuare una scelta inserendo un valore attraverso la tastiera. La sua strutura è: select <var> in <valore> do ......... ......... done le isctruzioni o le variabili che seguono "in" viene espanso, generando una lista di elementi. L'insieme delle parole espanse viene emesso attraverso lo standard error, ognuna preceduta da un numero. Se "in" (e i suoi argomenti) viene omesso, vengono utilizzati i parametri posizionali (`$1', `$2',...). Dopo l'emissione dell'elenco, viene mostrato l'invito contenuto nella variabile `PS3' e viene letta una riga dallo standard input. Se la riga consiste del numero corrispondente a una delle parole mostrate, allora il valore della variabile indicata dopo "select" viene posto a quella parola (cioè quella parola viene assegnata alla variabile). Se la riga è vuota, se è stato premuto soltanto Invio, l'elenco e l'invito vengono emessi nuovamente. Se viene letto il codice corrispondente a EOF ([Ctrl+d]), il comando termina. Qualsiasi altro valore letto fa sì che la variabile sia posta al valore della stringa nulla. La riga letta viene salvata nella variabile "REPLY". La lista di comandi che segue `do' viene eseguita dopo ciascuna selezione fino a che non viene incontrato un comando "break" o "return". Il valore restituito da "select" è quello dell'ultimo comando eseguito all'interno della lista "do", oppure zero se nessun comando è stato eseguito. Propongo un esempio che mostra uno script che fa apparire un menu composto dagli argomenti fornitigli; a ogni selezione mostra quello scelto: #!/bin/bash select i in $* do echo "hai selezionato $i premendo $REPLY" echo "" echo "premi Ctrl+c per terminare" done Per la comprensione di questo cosrutto è sicuramente molto più produttivo provarlo, "l'effetto" visivo vi aiuterà molto più delle parole. I cicli until La struttura di tale ciclo è: until condizione do ......... ......... done il ciclo ripeterà le operazioni ,(quelle che si trovano tra il "do" e il "done"), finchè la condizione è vere; un esempio: #!/bin/bash x=0 until [ "$x" -ge 10 ]; then echo "Current value of x: $x" x=$(expr $x + 1) done while La forma di questo ciclo è: while condizione do ........... ........... done Questo ciclo ripete le operazioni (quelle che si trovano tra "do" e "done") finchè la condizione non è vera; un esempio: #!/bin/bash x=0; while [ "$x" -le 10 ]; do echo "Il valore di x: $x" x=$(expr $x + 1) done questo programma non fa altro che settare a zero la variabile x, controlla nel while se il valore di x<=10 (vedi di seguito il significao di -le), se ciò è vero entra nel ciclo, stampa sullo schermo il valore corrente della variabile poi la incrementa di una unià la stessa.Quando la x>10 il programma termina.Vi propongo ora una lista di parametri del while: x x x x x x -eq -ne -gt -lt -ge -le num num num num num num x=num x!=num x>num x<y x>=num x<=num con != si indica "diverso"; questi parametri si utilizzano per il confronto dei numeri, per il confronto tra stinghe abbiamo: x=str uguaglianza tra x e str x!=str x e str diversi -n str controlla che str non sia "vuota" -z str controlla che str sia "vuota" Chiariamo ora cosa svolge l'operatore $(...); esso esegue il comando che contiene tra le parentesi for La struttura di queso ciclo è: for var in ... do ......... ......... done è un ciclo per un numero di ripetizioni note. Se ad esempio vogliamo stampare sullo schermo 3 volte una stringa: #!/bin/bash for var in 1 2 3; do echo "Gnu/linux" done la variabile var conterrà di volta in volta i valori da 1 a 3, stampando un Gnu/linux per ogni valore di var. Ancora un esempio: #!/bin/bash for i in a b c; do echo "i : $x" done verrà stampato il valore delle variabili a,b e c. Ancora un esempio: fare un'esempio piu' utile aggiungeremo il suffisso .txt a tutti i file nella directory corrente. #!/bin/bash for file in *; do mv $file $file.txt done queste righe rinominano con il suffisso ".txt" tutti i file contenuti nella directory corrente. CAPITOLO VI Le funzioni Anche la Bash dispone delle funzioni, sebbene in un'implementazione un limitata. Una funzione è un blocco di codice ,(chiamato subroutine), che rende disponibile una serie di operazioni specifiche. Ogni qual volta vi è del codice che si ripete per inteo o con leggere variazioni, allora è il momento di considerare l'uso di una funzione. La loro dichiarazione deve avere questa forma: function nome { comandi ....... } o anche: nome () { comandi ....... } e deve essere fatta prima di essere dichiarata; il contenuto tra parentesi graffe deve seguire le normali regole sintattiche degli operatori che si devono utilizzare. Per richiamare la funzione basta utilizzare il loro nome nel punto del programma desiderato. Ad esempio: #!/bin/bash pova () { echo "prova di una funzione" } prova dopo l'intestazione per l'interprete dichiariamo la funzione e mettiamo come unico comando quello per stampare sullo schermo; terminiamo la funzione (.... }) e la richiamiamo scrivendo semplicemente il suo nome.Il caso più frequente di utilizzo delle funzioni prevede che le funzioni lavorino su dei parametri che il progamma principale li fornisce, queste sono delle funzioni parametriche.Non vi sono distinzioni per quanto riguarda la loro dichiarazione piuttosto vi sono delle differenze per quanto riguarda il richiamo della stessa e sull'accesso al valore dei parametri. Il richiamo della funzione avviene seguendo questa sintassi: nome $arg1 $arg2 ............. $argn per accedere al loro valore dall'interno della funzione bisogna fare riferimento alle variabili di sisema posizionali "$1, $2,....,$n ".Un esempio chiarirà il tutto: #!/bin/bash param () { if [ -z "$1" ] then echo "non vi è arg1!!!" else echo "ag1: $1" fi if [ -z "$2" ] then echo "non vi è arg2!!!" else echo "arg2: $1" fi } in questo modo controlliamo se la lunghezza della variabile posiionale $1 prima e $2 dopo è zero allora non vi è nessun parametro in ingresso alla funzione. Ma se ad esempio vogliamo una funzione che fornisce la media di 3 numeri e restituisca tale media al programma principale per fare alte elaborazioni dobbiamo utilizzare l'operatore "return" nel seguente modo: #!/bin/bash media () { media=($1+$S2+$3)/3; return media } media 8 15 126 num=$? echo "$num" per utilizzare il valore di ritorno dalla funzione bisogna avere una variabile dedicata assegnandoli il valore con "$?" oppure se lo si vuole stampare direttamente come nell'esempio si può sostituire le righe: num=$? echo "$num" con la sola riga: echo "$?" Cè da dire ancora una cosa; utilizzando i parametri in questo modo essi hanno un limite che è quello di non dover superare il valore 256 (estremo incluso).Per "grandi" numeri invece bisogna utilizzare una variabile dedicata e globale nel seguente modo: #!/bin/bash rit= ritorno () { var=$1 rit=$fvar return } rit 33333 echo "valore di ritorno: $rit" dunque si "appoggia" la variabile posizionale in "var", a sua volta la si "riversa" nella variabile globale. CAPIOLO VII Espressioni aritmetiche La shell consente di risolvere delle espressioni su interi senza controllo dell'overflow, anche se la divisione per zero viene intercettata e segnalata come errore. Ecco una lista delgi operatori con una breve descrizione: -<op> <op1> <op1> <op1> <op1> <op1> <var> <op1> <op1> <op1> <op1> <op1> + <op2> - <op2> * <op2> / <op2> % <op2> = <valore> += <op2> -= <op2> *= <op2> /= <op2> %= <op2> Inverte il segno dell'operando. Somma i due operandi. Sottrae dal primo il secondo operando. Moltiplica i due operandi. Divide il primo operando per il secondo. Modulo o resto della divisione tra il primo e il secondo Assegnazione Abbreviazione di: <op1> = <op1> + <op2> Abbreviazione di: <op1> = <op1> - <op2> Abbreviazione di: <op1> = <op1> * <op2> Abbreviazione di: <op1> = <op1> / <op2> Abbreviazione di: <op1> = <op1> % <op2> Si possono effettuare tali operazioni in due modi; il primo già incontrato è: x=`expr $x + 1` il secondo è quello di inserire l'espressione da valutare tra doppie parentesi; lo stesso esempio diventa: x=$(($x+1)) come esempio provate: #!/bin/bash x=5 y=3 z=$(($x/$y)) echo "risultato della divisione: $z" AND ed OR Abbiamo già visto le strutture di controllo "if..elif..fi", ora aggiungiamo degli "operatori" molto utili. Parliamo degli operatori AND ed OR; l'AND è indicato con la sequenza di caratteri "&&" mentre, l'OR è indicato con "||" (doppio pipe, si trova sopra il tasto che contiene "\"). La sintassi della direttiva AND e': cond1 && cond2 viene controllata per prima la cond1, se è verificata allora controlla cond2 se dunque ambedue sono soddisfatte continua con l'esecuzione del codice successivo. Un esempio chiarificatore: #!/bin/bash x=5 y=10 if [ "$x" -eq 5 ] && [ "$y" -eq 10 ]; then echo "le condizioni sono verificate" else echo "le condizioni non sono verificate" fi provate a chambiare i valori di x e di y e vi renderete conto di come funziona. Il costrutto OR controlla la cond1 e se è falsa passa alla seconda, se almeno questa è vera esegue i comandi, altrimenti passa ai comandi successivi. Lo sesso esempio (con numeri diversi) visto prima diventa: #!/bin/bash x=3 y=2 if [ "$x" -eq 5 ] || [ "$y" -eq 2 ]; then echo "Una delle condizioni e' vera" else echo "Nessuna delle condizioni e' vera" fi Questi costrutti servono per evitare l'annidarsi di più istruzioni "if", anche se nessuno ci impedisce di utilizzarli. APPENDICE I permessi sui file A differenza di ciò che avviene nei sistemi windows i permessi e il concetto di appartenenza di un file nei sistemi linux like è molto sentito. Ogni file in linux è di proprietà di un utente e di un gruppo. Gli utenti possiedono i file creati da loro stessi, e quelli attribuiti all'utente dall'utente root con il comando "chown". Il gruppo di un file viene dedotto dal sistema in base al gruppo di appartenenza dell'utente, visto che un utente puo' appartenere a piu' gruppi puo' cambiare gruppo di appartenenza a un file con il comando "chgrp". Si puo' sapere a che gruppo appartenga un utente usando il comando "groups". Vi è una rappresentazione numerica dei permessi che vede tre categorie utente-gruppo-altri, che venngono (ciascuna dei tre permessi), considerate come una sequenza di tre cifre in formato ottale. Ogni cifra ottale corrisponde a tre cifre in binario (base 2), che sono sequenze di uno e di zero, quindi abbiamo tre gruppi di tre cifre che ci permettono di settare tutti i permessi. Ogni cifra ottale ci restituisce tre cifre binarie, per esempio la cifra ottale 6 corrisponde al numero binario 110 che quindi imposta a 1 il permesso di lettura, a 1 il permesso di scrittura e a 0 quello di esecuzione. una breve tabella: ottale 0 1 2 3 4 5 6 7 binario 000 001 010 011 100 101 110 111 un numero ottale ha il corrispondente binario che puo' essere interpretato come una terna di valori booleani. Si ricava un altra tabella: permesso r w x numero 4 2 1 binario 100 010 001 per determinare il numero totale da specificare per ogni elemento della terna utentegruppo-altri bastera' sommare i valori relativi; esempio: proprietario rx 4+1=5 gruppo r 4 altri r 4 544 ovvero 101 100 100 digitando chmod 544 nome_file gli attribuiremo i seguenti permessi r-xr--r-- Ridirezione dell'I/O La shell bash offre degli operatori di ridirezione che di ridirigere l'output o l'input di un programma affinchè si possa uilizzare gli stessi come input o output a loro volta di altri programmi; vediamo come: l'operatore ">" permette di salvare l'output di un programma su un file. Esso prende i dati direttamente dallo standard output senza mostrare alcun messaggio sullo schermo, un esempio: #!/bin/bash ls > list.txt in questo modo si memorizzare l'output del comando ls all'interno del file list.txt. l'operatore "<" invece permette di ridirigere il contenuto di un file nello standard input di un programma, ad esempio: #!/bin/bash less < list.txt in questo modo il contenuto del file list.txt con l'ausilio del comando less sarà reimpagino e stampato sullo schermo. Esiste un operatore molto importane chiamato pipe, caratterizzato dal simbolo '|', che svolge il compito di redirigere l'output di un programma come input di un altro programma, un esempio: #!/bin/bash ls | less queste righe sostiuisco quelle precedenti evitando dunque di creare un file perchè grazie al pipe avremo direttamente sullo schermo il risultato del comando ls.Questo è nauralmente un esempio perchè il solo comando "ls" stampa sullo schermo il contenuto della directory, seve solo per far capire quano utile sono le pipe line. L'ultimo operatore che analizzeremo è ">>", esso svolge una funzione analoga all'operatore '>' ma aggiunge in coda al contenuto del file su cui si effettuerà la ridirezione, l'output generato dal programma preservandone il contenuto mentre l'operatore ">" invece comporta la cancellazione del contenuto del file di destinazione. I commenti Capita spesso di cancellare prima e risprendere subito dopo una linea di codice o di voler mettere dei commenti semplicemente per migliorare la leggibilià del programma o per lasciare traccia per revisioni future. Per facilitare ciò tutti i linguaggi di programmazione permettono di rendere come commento una o più linee di codice semplicemente facendole precedere da un carattere; nel nostro caso il carattere è #: #sono un commento è importante dire che quando questa linea sarà processata dall'interprete (ma in generale vale anche per i linguaggi compilati) essa sarà saltata. Gli alias Possiamo definire gli alias come comandi "ridotti" ovvero se ad esempio vogliamo chiamare un comando lungo perchè ha molti parametri da passare in maniera piu' mnemonica possiamo assegnarli un alias come il caso in esempio: ls -aF --color potremmo assegnare l'alias nel file /etc/bashrc oppure ~/.bashrc nel seguente modo: alias ls='ls -aF --color' così facendo tutte le volte che digiteremo semplicemente ls la shella eseguirà ls con tutti i suoi parametri. Non vi sono limii per le definizioni di ls, se ne possono definire quanti se ne vogliono.