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.

Cos'è uno script

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:

Tipi di script

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).

Eseguire uno script

Per eseguire uno script sono possibili due soluzioni:


Script di Shell

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.

Variabili e parametri

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 dell’utente), 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".

Quoting

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.

Reindirizzamento

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 l’espressione com>testo (equivalente a com 1> testo). Per reindirizzare lo standard error è invece necessario l’espressione 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 l’operatore "<". 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 "|").

Comandi esterni

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:

Notare l'utilizzo del comando cut, come filtro (di colonna) per selezionare solo i valore desiderato. L'output del comando uname è del tipo:

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.

Strutture condizionali

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:

Test su file, numeri e stringhe

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:

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"].

Case

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 è:

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 "|"):

Strutture cicliche

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:

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):

Interfaccia utente

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:

L'esempio seguente mostra una finestra di conferma (yes/no), la variabile $? vale 0 solo in caso di yes:

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).

L'ultimo esempio, più complesso, mostra utilizzo di un menu a scelta multipla. Per ulteriori informazioni vedere man dialog.

Debug

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.

Per saperne di più

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