News About Copertina Confessioni |
Articoli
Programmazione mediante Script
Ogni nuovo utente Linux, passata la prima fase di "ambientazione", si troverà prima o poi con la necessità di realizzare piccoli programmi e semplici automatismi per sfruttare appieno il proprio sistema o, semplicemente, per velocizzare alcune operazioni altrimenti noiose e ripetitive.
Per risolvere la maggior parte dei problemi non sarà necessario ricorrere alla vera e propria programmazione Unix, mediante l'uso del C (o di altri linguaggi compilati), ma basterà sfruttare alcuni semplici, ma efficaci, strumenti che Linux (come ogni altro Unix) mette a disposizione.
Con questo articolo cercherò fornire un minimo di conoscenza per riuscire a realizzare dei semplici "programmi", mediante l'utilizzo del "linguaggio di script". La filosofia stessa di Unix prevede l'utilizzo di piccoli programmi fortemente specializzati, che combinati insieme possono realizzare numerose funzioni.
Ogni sistema Unix mette a disposizione dell'utente lo script language, che è
uno strumento molto potente per realizzare piccoli programmi.
Tali programmi sono costituiti da semplici file di testo, che a loro volta contengono
una sequenza di istruzioni (uno script appunto), con la possibilità di utilizzare
condizioni (if
e then
), cicli (for
) e altri
costrutti tipici di un linguaggio di programmazione.
Queste istruzioni vengono in seguito interpretate ed eseguite dalla shell di sistema, per questo il formato di questi script dipende fortemente dal tipo di shell utilizzata. Il linguaggio di script è quindi un linguaggio interpretato, in contrapposizione con i linguaggi come il C che sono compilati.
Nella realtà in Unix il concetto di script è stato ulteriormente generalizzato e si hanno vari tipi di linguaggi di script, a seconda del tipo di interprete.
Per distinguere il tipo di interprete, la prima riga dello script contiene il nome e il percorso del programma necessario per eseguire lo script stesso (in questo modo si garantisce l'utilizzo della shell più appropriata, nel caso ve ne siano più di una a disposizione). Il formato è del tipo:
#!/bin/sh
In Linux la shell principalmente utilizzata è bash
(Bourne Again Shell),
che offre numerose funzionalità; gli script di shell che vedremo di seguito negli
esempi faranno riferimento proprio alla bash
(su Linux sh
è
semplicemente un link simbolico a bash
).
Storicamente un'altra shell molto famosa è la tcsh
, che prevede
una sintassi degli script molto simile alla sintassi del C.
Come detto in precedenza, oltre agli script di shell (interpretati cioè da una shell),
sono disponibili anche script di altri programmi, tra cui awk
, perl
e tcl
(questo script, mediante il front-end grafico tk
,
consente di realizzare velocemente applicazioni per X).
Per eseguire uno script sono possibili due soluzioni:
/bin/sh nome_script
.chmod +x nome_script
, a questo punto sarà sufficiente chiamare
lo script con il suo nome, come un qualsiasi altro comando.Uno script di shell non è altro che un file di testo che contiene una serie di comandi e di costrutti di shell Per ulteriori informazioni sulla sintassi e sui comandi di shell riferirsi alla man-page di bash.
Per convenzione la prima riga deve essere sempre #!/bin/sh
(senza
ulteriori commenti). Il carattere usato per i commenti è proprio il simbolo
"#
" (tutto ciò che segue il "cancelletto" è ignorato dalla shell).
L'utilizzo più semplice degli script di shell è come file "macro" per lanciare più comandi in successione (in questo caso lo script non è altro che un elenco di comandi). L'utilizzo più "professionale" è la realizzazione di veri è propri programmi che utilizzano parametri passate da linea di comando e variabili interne.
Le variabili sono delle stringhe alfanumeriche e il loro valore viene
identificato da $nome_variabile
. È importante notare che per la
shell la variabile VAR1
è diversa da var1
(Unix e la maggior parte dei sui programmi distinguono tra maiuscolo e minuscolo).
Per inizializzare una variabile si usa l'espressione var=stringa
(senza alcun spazio prima e dopo il segno d'uguale), dove stringa
deve essere racchiusa tra virgolette (o apostrofi) se contiene degli spazi
(i caratteri speciali vanno invece preceduti da "\
").
Oltre alle variabili interne, in uno script di shell è possibile accedere
alle variabili d'ambiente (le stesse che vengono visualizzate dal comando
env
). Di solito, per convenzione, le variabili d'ambiente sono indicate
con caratteri maiuscoli, come PATH
(contiene il percorso di ricerca dei
file eseguibili) e PS1
(contiene la stringa del prompt di primo livello).
Per esportare una variabile locale in quelle d'ambiente si utilizza il comando
export
(nella tcsh
il comando equivalente è
setenv
).
Alcune variabili vengono direttamente gestite dalla shell, come ad esempio
UID
(ID dellutente), OSTYPE
(tipo del sistema operativo),
HOSTYPE
(tipo di macchina) e molte altre.
Per un elenco completo delle variabili di shell e delle variabili d'ambiente
utilizzate vedere man bash
.
I parametri dello script possono essere letti mediante i simboli $n
(con n
intero positivo), in particolare $0
contiene il nome
dello script, $1
contiene il primo parametro, $#
contiene il
numero di parametri e $*
contiene tutta la riga dei parametri.
Ad esempio, con la riga di comando script_exe par1 par2
, i parametri
assumeranno i seguenti valori: 0=script_exe
, 1=par1
,
2=par2
, #=2
e *="par1 par2"
.
Con il termine di quoting si indica l'utilizzo di apici e virgolette per
racchiudere stringhe di caratteri. Ad esempio per visualizzare una frase si possono
usare indistintamente i due comandi echo "frase"
oppure echo 'frase'
(di solito si utilizzano le virgolette se la frase contiene degli apostrofi o si
utilizza l'apostrofo se la frase contiene le doppie virgolette).
L'utilizzo dell'accento grave (carattere "`
") determina un risultato
totalmente differente: tutto ciò che è racchiuso dalla coppia di caratteri
viene interpretato come un comando. In questo modo è possibile assegnare ad
una variabile il risultato di un comando.
Per inserire caratteri speciali in stringhe o espressioni bisogna utilizzare il
carattere di escape "\
", usato anche per andare "a capo" su una nuova
riga senza interrompere il comando.
Lavorando in ambiente di shell è importante aver ben chiaro il concetto
fondamentale di standard input (stdin
), standard output (stdout
)
e standard error (stderr
).
Ad ogni comando che viene eseguito vengono "aperti" questi tre flussi e la shell
consente un facile reindirizzamento degli stessi verso altri comandi e verso file.
Nella bash
i tre flussi vengono numerati rispettivamente con 0, 1 e 2.
Per reindirizzare il risultato del comando com
verso il file "testo",
è sufficiente utilizzare lespressione com>testo
(equivalente
a com 1> testo
). Per reindirizzare lo standard error è
invece necessario lespressione com 2> errori
.
Per utilizzare sia il flusso 1 che il flusso 2 si utilizza "&>
".
È importante notare che il reindirizzamento su un file già esistente
ha come risultato la sovrascrittura del file stesso (a meno che la variabile
noclobber
sia settata); per aggiungere in coda ad un file si utilizza
il ">>
".
Per il flusso 0, raramente si utilizza il reindirizzamento (visto che la maggior
parte dei comandi prevede come argomento proprio un file di ingresso), comunque è
disponibile loperatore "<
". L'utilizzo dell'operatore
"<<
" ha un significato particolare (rispetto al corrispondente
per lo standard output): in uno script di shell equivale ad accettare in input tutto
il resto del file, fino ad un carattere specificato a destra di "<<
"
(di solito EOF
, ma un carattere qualsiasi va bene).
In questo modo è possibile mischiare dati e script.
L'operatore che concatena lo standard output di un programma con lo standard
input di un altro programma è la cosiddetta "pipe" (indicata con il carattere
"|
").
In ambiente di shell è possibile richiamare ed utilizzare comandi di
sistema (o altri script) o direttamente o mediante il quoting (con il carattere
"`
") visto in precedenza. Quando un comando chiamato direttamente
termina la sua esecuzione restituisce il controllo allo script; per sapere se il
comando è andato a buon file è disponibile una variabile particolare
(indicata con ?
) che contiene il codice di uscita dell'ultimo comando.
Il test [$? = 0]
verifica che non vi siano stati errori (per
convenzione i comandi restituiscono un valore diverso da 0 quando si è
riscontrato un qualche problema).
All'interno di uno script, per fornire condizioni d'uscita diverse da 0
(ad esempio per segnalare un errore), è possibile utilizzare il comando
exit num
, che sospende l'esecuzione dello script e restituisce il
valore num
(nella variabile ?).
I comandi richiamati mediante quoting, vengono impiegati per ottenere informazioni a run-time. A esempio, per visualizzare la versione del kernel è possibile utilizzare il seguente comando:
echo "La versione del kernel è `/bin/uname -a | /bin/cut -d\ -f3`."
Notare l'utilizzo del comando cut
, come filtro (di colonna) per selezionare
solo i valore desiderato. L'output del comando uname
è del tipo:
Linux linbox 2.0.22 #1 Wed Oct 9 12:04:27 MET DST 1996 i486
Per consentire la comunicazione tra script diversi (o tra script e comandi) è
possibile utilizzare i comandi trap
(per accettare un segnale) e
kill
(per inviare un segnale).
Specificando in uno script l'istruzione trap "comando" segnale
,
appena che arriva il segnale specificato, viene eseguito il comando corrispondente.
Per realizzare condizioni all'interno di script di shell si utilizza il costrutto
if then else fi
. Tali strutture si possono nidificare una nell'altra
ed esiste anche una sintassi speciale (elif
), per evitare di generare
troppe coppie if fi
. Per uscire direttamente dal costrutto si utilizza
il break
.
L'argomento di if
è un "test" ossia un'espressione che restituisca
un valore vero o falso; nella bash
la funzione per realizzare il test
è già inclusa e si utilizzano le parentesi quadre per racchiudere
l'espressione del test (altre shell utilizzano il comando esterno test
).
Esistono numerosi tipi di test, negli esempi vedremo alcuni esempi di condizioni su
file e su variabili.
Per eseguire il "demone del kernel" (per la gestione automatica dei moduli), solo se il comando è presente:
if [ -x /sbin/kerneld ] # -x è vero solo se il file esiste then /sbin/kerneld else echo "Non è possibile eseguire kerneld" fi
I test sui file disponibili per la programmazione di script sono: -x
(vero solo se il file esiste), -s
(vero solo se il file esiste e non
è vuoto), -d
(vero solo se il file è una directory),
-f
(vero solo se il file è realmente un file), -r
(vero solo se si hanno i diritti in lettura al file), -w
(vero solo se
si hanno i diritti in scrittura al file). Per ulteriori informazioni fare riferimento
a man test
.
Un discorso a parte vale per la gestione delle variabili, poiché la shell non gestisce direttamente le variabili numeriche (ogni variabile è semplicemente una stringa).
Per contro sono disponibili test specifici per variabili numeriche.
È possibile valutare l'uguaglianza di una variabile numerica con un certo
valore, come [$numero -eq 0] che
restituisce vero se la variabile
numero
è diversa da 0. In alternativa a -eq
è
possibile usare il segno di uguale (ma sempre preceduto e seguito da uno spazio).
D'altronde questo tipo di test restituirebbe vero anche se venisse condotto a
livello di stringa (ossia mediante [$numero -eq "0"]
).
I test specifici per le variabili numeriche sono: -gt
(vero se maggiore),
-lt
(vero se minore), -ge
(vero se maggiore o uguale),
-le
(vero se minore o uguale) , -eq
(vero se uguale).
Il problema delle variabili numeriche viene a galla tentando di modificare il valore
della variabile stessa. Supponiamo per esempio che count
valga 1 e si
desidera incrementare il valore di 2. Purtroppo dopo l'assegnazione count=$count+2
il nuovo valore di count sarà 1+2 (perché la shell considera tutto
ciò che è alla destra di una assegnazione come un'unica stringa).
Per effettuare operazioni matematiche sulle variabili è necessario utilizzare
il comando esterno expr
, la sintassi corretta diventa quindi:
count=`expr $count + 2` # è importante rispettare gli spazi prima e dopo il segno +
expr
, mentre non devono essere usati per l'assegnazione
di count
. Volendo utilizzare expr
per operazioni di
moltiplicazione bisogna utilizzare \*
(questo perché l'asterisco
ha un particolare significato per la shell).
I test specifici per le variabili stringa sono invece l'uguaglianza (=
),
la disuguaglianza (!=
) e la verifica sulla lunghezza della stringa
(-z
per verificare la lunghezza nulla e -n
per verificare
che la stringa sia non vuota). Per considerare anche gli spazi all'interno delle
stringhe usare ["str1" = "$str2"]
.
Un altro costrutto condizionale disponibile per la programmazione di script
è il case esac
, utile nel caso in cui la struttura if then
elif then else fi
diventi troppo sviluppata. La sintassi generale è:
case stringa in stringa1) se stringa è uguale a stringa1 esegui tutti i comandi fino al doppio ";" ed ignora tutti gli altri casi;; stringa2) se stringa è uguale a stringa2 esegui tutti i comandi fino al doppio ";" ed ignora tutti gli altri casi;; stringa3) se stringa è uguale a stringa3 esegui tutti i comandi fino al doppio ";" ed ignora tutti gli altri casi;; esac
Ad esempio per visualizzare un messaggio diverso a seconda del "tipo" di file
(notare l'utilizzo del carattere jolly "*
" e dell'operatore "or"
costituito dal carattere "|
"):
for file in `ls . ` ; do # per ogni elemento della directory corrente ... case $file in # visualizza un messaggio appropriato *.gif|*.jpg) echo "$file: file grafico" ;; *.txt|*.text) echo "$file: file di testo" ;; *.c|*.f|*.for) echo "$file: file sorgente" ;; *) echo "$file: file generico" ;; esac done
Per realizzare dei cicli all'interno di script di shell si utilizza principalmente
il costrutto for do done
. L'argomento di for
è una
variabile con un elenco o una lista di valori che la stessa può assumere.
In alternativa l'argomento può essere un nome di file (anche con i caratteri jolly).
Ad esempio, per abilitare la tastiera numerica (BLOCK NUM
) su tutte le
console virtuali:
for t in 1 2 3 4 5 6 do setleds +num < /dev/tty$t > /dev/null done
Un altro costrutto per realizzare i cicli è while do done
.
In questo caso l'argomento di while
è un "test" tipico dei
costrutti if
. Il costrutto analogo, ma con il test invertito è
until do done
, in entrambi i casi il test è sempre
all'inizio del ciclo do done
.
A esempio, un semplice programma che elabora più file contemporaneamente
(notare l'utilizzo del separatore di comandi ";
" per mettere più
comandi su una stessa riga):
if [ $# -eq 0]; then echo "Mancano argomenti..."; fi until [ $# -eq 0]; do # la variabile "#" contiene il numero di parametri passati if [ ! -s $1] then echo "File $1 non esistente" else comandi_specifici fi shift # sposta le variabili a sinistra (1=$2, 2=$3, ...) done
Benché non sia prevista nell'ambiente di shell, è possibile realizzare
un'interfaccia utente mediante il comando esterno dialog
(non è
particolarmente veloce ma il risultato può essere più che soddisfacente).
Ad esempio, per visualizzare il contenuto di un file in una finestra in attesta di una conferma:
dialog --title "Finestra informativa" --textbox "/path/file_name" 22 77
L'esempio seguente mostra una finestra di conferma (yes/no), la variabile
$?
vale 0 solo in caso di yes:
dialog --title "Confermare" --clear --yesno "Siete sicuri?" 10 30
Quest'altro esempio utilizza una finestra per immettere un parametro. Per assegnare
il parametro ad una variabile utilizzare var1 = `cat /tmp/f; rm /tmp/f`
(bisogna cancellare il file temporaneo).
dialog --title "Input valore" --clear --inputbox "Immettere il valore" 10 30 default 2> /tmp/f
L'ultimo esempio, più complesso, mostra utilizzo di un menu a scelta multipla.
Per ulteriori informazioni vedere man dialog
.
dialog --title "Menu a scelta multipla" --menu \ "\nSelezionare una delle seguenti voci:\n" 14 65 2 \ "1" "Possibilità numero 1" \ "2" "Possibilità numero 2" 2> /tmp/temp if [ $? = 1 -o $? = 255 ]; then rm /tmp/temp ; exit # in caso di errore termina lo script fi scelta="`cat /tmp/temp`" ; rm -f /tmp/temp if [ "$scelta" = "1" ]; then # in alternativa a if si può usare case echo "Hai scelto la voce numero 1" elif [ "$scelta" = "2" ]; then echo "Hai scelto la voce numero 2" fi
Per "collaudare" gli script di shell sono disponibili due opzioni particolari
della shell: -v
(modalità "verbose") e -x
(modalità "execute"). Con -v
la shell visualizza ogni comando
prima di eseguirlo, mentre con -x
si visualizza ogni comando eseguito
con le relativa variabili ad esso associate.
In entrambi i casi per eseguire lo script è necessario utilizzare la
forma sh -v script
o sh -x script
.
Il filesystem di Linux è pieno di esempi di script di shell, in particolare
i file rc
(eseguiti durante l'inizializzazione del sistema, nella
Slackware si trovano in /etc/rc.d/
) e i file profile
(che contengono la configurazione della bash e si trovano in /etc/profile
e ~/.profile
), sono tutti script di shell.
Nella distribuzione Slackware, inoltre, lo stesso programma di setup
e il pkgtool
sono degli script di shell; nella directory
/usr/lib/setup/
sono disponibili tutti i file.
In letteratura esistono numerosi testi su Unix che contengono informazioni utili (ed esempi) per la programmazione di shell; ad esempio un testo per chi vuole iniziare è il libro di Henry McGilton e Rachel Morgan "Il sistema operativo Unix", edito dalla McGraw-Hill.
Come accennato in precedenza esistono anche altre shell (oltre alla tcsh
in Linux è disponibile la ksh
, la zsh
e la
ash
), che utilizzano diverse sintassi e offrono funzionalità aggiuntive.
di Andrea Mauro
News About Copertina Confessioni |