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.