Nella precedente lezione è stata introdotta la Bourne shell come esempio di linguaggio di comandi per l'ambiente Unix. Tuttavia, l'eccessiva semplicità di tale interprete va a discapito della chiarezza e immediatezza di lettura di molti "script" di shell, anche quando questi trattano problematiche standard. Allo scopo di mettere a disposizione dell'utente finale un ambiente di lavoro più amichevole il sistema operativo Linux ha sviluppato un interprete più sosfisticato, noto col nome di bash shell.
Nel corso della lezione verranno ripresi ed ampliati i costrutti linguistici già visti per la Bourne shell. Inoltre, verranno considerati ed approfondite le proprietà dei parametri di shell.
L'interprete dei comandi di un sistema operativo è l'interfaccia testuale fra l'utente e il sistema. In questo modo l'utente finale è in grado di accedere a tutte le funzionalità delle risorse disponibili dell'elaboratore. Tale interprete è uno degli strumenti essenziali che l'amministratore del sistema impiega per la sua gestione.
Come ogni linguaggio, anche l'inteprete dei comandi, o shell, è caratterizzato da un insieme di comandi base, una serie di regole per costruire comandi composti entrambi potendo utilizzare variabili, ossia identificatori esplicitamente impiegati per trasferire valori da un comando all'altro.
Un comando semplice è una sequenza opzionale di assegnazioni di variabile seguita da parole, separate da blank, e ridirezioni, e terminati da un operatore di controllo. La prima parola specifica il comando che deve essere eseguito. Le rimanenti parole sono passate come argomenti per il comando chiamato.
Il valore di ritorno di un comando semplice è il suo stato di uscita, o 128+n se il comando è terminato dal segnale n.
Una pipeline è una sequenza di uno o più comandi separati dal carattere |. Il formato per una pipeline è:
Lo standard output di comando è connesso allo standard input di comando2. Questa connessione è effettuata prima di qualsiasi ridirezione specificata dal comando.
Se la parola riservata ! precede una pipeline, lo stato di uscita di quella pipeline è il NOT logico dello stato di uscita dell'ultimo comando. Altrimenti, lo stato della pipeline è lo stato di uscita dell'ultimo comando. La shell aspetta che tutti i comandi nella pipeline terminino, prima di ritornare un valore.
Ogni comando in una pipeline è eseguito come un processo separato (cioè, in una subshell).
Una lista è una sequenza di una o più pipeline separate da uno degli operatori di controllo ;, &, &&, o ||, e terminata da uno di ;, &, o <newline>.
Di questi operatori di lista, && e || hanno uguali precedenze, seguiti da ; e &, che hanno uguali precedenze.
Se un comando è terminato dall'operatore di controllo &, la shell esegue il comando in background in una subshell. La shell non aspetta che il comando finisca, e lo stato di ritorno è 0. I comandi separati da un ; sono eseguiti sequenzialmente; la shell aspetta che ogni comando, a turno, termini. Lo stato di ritorno è lo stato di uscita dell'ultimo comando eseguito.
L'operatore di controllo && e || denotano liste AND e liste OR, rispettivamente. Una lista AND ha la forma
comando2 è eseguito se, e solo se, comando ritorna uno stato di uscita di zero.
Una lista OR ha la forma
comando2 è eseguito se, e solo se, comando ritorna uno stato di uscita diverso da zero. Lo stato di ritorno di liste AND e OR è lo stato di uscita dell'ultimo comando eseguito nella lista.
Un comando composto è uno dei seguenti:
lista è eseguita in una subshell. Assegnamenti di variabile e comandi incorporati che influenzano l'ambiente della shell non lasciano effetti dopo che il comando è completato. Lo stato di ritorno è lo stato di uscita di lista
lista è semplicemente eseguita nell'ambiente di shell corrente. Questo è conosciuto come group command. Lo stato di ritorno è lo stato di uscita di lista.
La lista di parole seguenti in è espansa, generando una lista di elementi. La variabile nome viene assegnata, di volta in volta, a ciascun elemento di questa lista e lista è eseguita ogni volta. Se la parola in viene omessa, il comando for esegue lista una volta per ogni parametro posizionale che è stato assegnato
La lista di parole seguenti ?espansa, generando una lista di elementi. L'insieme delle parole espanse è stampato sullo standard error, ognuna preceduta da un numero. Se la parola in è omessa, sono stampati i parametri posizionali. Viene poi mostrato il prompt PS3 e viene letta una linea dallo standard input.
Se la linea consiste del numero corrispondente ad una delle parole mostrate, allora il valore di nome viene assegnato a quella parola. Se la linea è vuota, le parole e il prompt sono mostrati di nuovo. Se viene letto EOF, il comando termina. Qualsiasi altro valore letto fa sì che nome sia posto al valore nullo. La linea letta è salvata nella variabile REPLY.
La lista è eseguita dopo ciascuna selezione fino a che non sia eseguito un comando break o return. Lo stato di uscita di select è lo stato di uscita dell'ultimo comando eseguito in lista, oppure zero se nessun comando è stato eseguito.
Un comando case prima espande parola, e prova a confrontarla, a turno, con ognuno dei pattern, usando le stesse regole di confronto dell'espansione dei pathname dei file. Quando viene trovata una coincidenza, viene eseguita la lista corrispondente.
Dopo il primo confronto riuscito, non ne viene controllato nessuno dei successivi. Lo stato di uscita è zero se nessun pattern combacia. Altrimenti, esso è lo stato di uscita dell'ultimo comando eseguito in lista.
La lista dopo if è eseguita. Se il suo stato di uscita è zero, viene eseguita la lista dopo then. Altrimenti, viene eseguita a turno ciascuna lista dopo elif, e se il suo stato di uscita è zero, viene eseguita la corrisponddente lista dopo then e il comando termina. Altrimenti, se presente, viene eseguita, la lista dopo else.
Lo stato di uscita è lo stato di uscita dell'ultimo comando eseguito, o zero se nessuna delle conditioni provate risulta vera.
Il comando while esegue continuamente la lista dopo do fin tanto che l'ultimo comando in lista ritorna uno stato di uscita pari a zero. Il comando until è identico al comando while, con la sola differenza che il risultato del test è negato; la lista dopo do è eseguita fin tanto che l'ultimo comando in lista ritorna uno stato di uscita diverso da zero.
Lo stato di uscita dei comandi while e until è lo stato di uscita dell'ultimo comando eseguito nella lista dopo do o zero se non ne è stato eseguito alcuno.
Questo definisce una funzione chiamata nome. Il corpo della funzione è la lista di comandi tra { e }. Questa lista viene eseguita ogni volta che nome viene specificato come nome di un comando semplice.
Lo stato di uscita di una funzione è lo stato di uscita dell'ultimo comando eseguito nel suo corpo.
Un parametro è una entità che immagazzina valori, qualcosa come una variabile in un linguaggio di programmazione convenzionale. Esso può essere un nome, un numero, o uno dei caratteri speciali elencati come parametri speciali. Per gli scopi della shell, una variabile è un parametro indicato da un nome.
Un parametro risulta impostato se ad esso è stato assegnato un valore. La stringa nulla è un valore valido. Una volta che una variabile è impostata, essa può essere eliminata solo usando il comando predefinito unset.
Una variabile può essere assegnata da una istruzione della forma
Se valore non viene dato, alla variabile viene assegnata la stringa nulla. Tutti i valori sono sottoposti alla espansione della tilde, espansione di parametro e variabile, sostituzione di comando, espansione aritmetica, e rimozione dei caratteri di quotatura. Se la variabile ha il suo attributo assegnato allora valore è sottoposto alla espansione aritmetica perfino se la sintassi $[...] non appare.
La suddivisione in parole non viene effettuata, con l'eccezione $@ come spiegato più avanti per i Parametri speciali. L'espansione del pathname in questo caso non viene effettuata.
Un parametro posizionale è un parametro indicato da una o più cifre, diverse dalla singola cifra 0. I parametri posizionali sono assegnati dagli argomenti della shell quando viene chiamata, e possono essere riassegnati usando il comando predefinito set.
I parametri posizionali non possono essere assegnati con istruzioni di assegnazione. I parametri posizionali sono temporaneamente sostituiti quando viene eseguita una funzione di shell.
Quando si espande un parametro posizionale consistente di più di una sola cifra, esso deve essere racchiuso tra parentesi graffe.
La shell tratta molti parametri in modo speciale. Questi parametri possono solo essere referenziati; il loro assegnamento non è permesso.
Si espande nei parametri posizionali, a partire da uno. Quando l'espansione avviene tra virgolette, esso si espande in una singola parola con il valore di ogni parametro separato dal primo carattere della variabile speciale IFS. Cioè, ``$*'' è equivalente a ``$1c$2c...'', dove c è il primo carattere del valore della variabile IFS.
Se IFS è nulla o non assegnata, i parametri sono separati da spazi.
Si espande nei parametri posizionali, a partire da uno. Quando l'espansione avviene tra virgolette, ogni parametro si espande in una parola separata. Cioè, ``$@ è equivalente a ``$1'' ``$1'' ... Quando non vi è alcun parametro posizionale, ``$@'' e $@ si espandono in nulla (cioè, sono rimossi).
Si espande nel numero di parametri posizionali in decimale.
Si espande nello stato della pipeline in foreground più recentemente eseguita.
Si espande nei flag di opzione correnti come specificato in base alla chiamata, dal comando predefinito set, o quelli assegnati dalla shell stessa.
Si espande nel ID di processo della shell. In una subshell (), esso si espande nel ID di processo della shell corrente, non la subshell.
Si espande nel ID di processo del comando in background (asincrono) più recentemente eseguito.
Si espande nel nome della shell o script di shell. Questo viene asseganto alla inizializzazione della shell. Se la shell è chiamata con un file di comandi, $0 è assegnato al nome di quel file. Se la shell è avviata con l'opzione -c, allora $0 è assegnato al primo argomento dopo la stringa che deve essere eseguita, se ve ne è una presente. Altrimenti, esso è posto al percorso usato per chiamare la shell, secondo quanto specificato come dato dall'argomento zero.
Si espande nell'ultimo argomento del precedente comando, dopo l'espansione. Viene anche asseganto al nome completo di ogni comando eseguito e messo nell'ambiente ed esportato da quel comando.
Negli esempi che seguono sono riportati alcune caratteristiche salienti sia della Bourne shell che della C shell. In essi compaiono alcuni comandi di utilità generale il cui impiego e spiegazione dettagliaia possono ottenersi consultando il manuale in linea con man <nomecomando>.
Il seguente programma permette di cancellare indifferentemente file o direttori, trasferendoli temporaneamente in un direttorio cestino dal quale possono essere recuperati o definitivamente rimossi. La parte iniziale del programma serve a verificare il numero e la correttezza dei parametri passati. Vengono uitlizzati i programmi di utilità generale sed e expr.
# # delete options file # delete options dir # NAMES="" K=0 OPTIONS="" for ITEM in $* do if [ `echo $ITEM | egrep -e "-"` ] > /dev/null; then OPTIONS="$OPTIONS `echo $ITEM | sed s/-//`" else K=`expr $K + 1` NAMES="$ITEM $NAMES" fi done if [ $K -eq 0 ]; then echo "insufficient parameters" exit 1 fi OPT="" MINUS="-" VERBOSE=0 for A in $OPTIONS do if [ $A = "v" ] > /dev/null; then VERBOSE=1 else OPT="$MINUS$A $OPT" fi done for NAME in $NAMES do if [ $VERBOSE -eq 0 ] > /dev/null; then mv $OPT $NAME $HOME/.wastebasket else echo "delete $NAME (y/n) \c" read YES if [ $YES = "y" ] > /dev/null; then mv $OPT $NAME $HOME/.wastebasket fi fi done |
Cancellazione di un File |
Il seguente programma estende il comando mv spostando il file passato come primo argomento nel direttorio indicato come secondo, eventualmente creandolo se questo non esiste. La parte iniziale del programma serve a verificare il numero e la correttezza dei parametri passati. Vengono utilizzati i programmi di utilità generale sed e expr.
# # move options file dest # NAMES="" K=0 OPTIONS="" for ITEM in $* do if [ `echo $ITEM | egrep -e "-"` ] > /dev/null; then OPTIONS="$OPTIONS `echo $ITEM | sed s/-//`" else K=`expr $K + 1` NAMES="$ITEM $NAMES" fi done if [ $K -le 1 ]; then echo "insufficient parameters" exit 1 fi OPT="" MINUS="-" for A in $OPTIONS do OPT="$MINUS$A $OPT" done K=1 for NAME in $NAMES do if [ $K -eq 2 ]; then FNAME=$NAME else DEST=$NAME fi K=`expr $K + 1` done if [ ! -d $DEST ] && \ [ `echo $DEST | egrep -e "/"` ] > /dev/null; then mkdir $DEST fi mv $OPT $FNAME $DEST |
Rinomina un File |
Il seguente programma restituisce, per ogni file, le sue caratteristiche essenziali: leggibile, eseguibile, scrivibile, vuoto, direttorio, ecc...
#!/bin/bash # ftype: Display the type of file # Synopsis : ftype <filename> # if [ "$#" != 1 -o "$1" == "" ] then FILE="." else FILE="$1" fi echo "$FILE" is: if [ -r "$FILE" ] then echo -n "readable, " fi if [ -w "$FILE" ] then echo -n "writable, " fi if [ -x "$FILE" ] then if [ -d "$FILE" ] then echo -n "searchable, " else echo -n "executable, " fi fi if [ -f "$FILE" ] then echo -n "regular file " fi if [ -d "$FILE" ] then echo -n "directory " fi if [ -z "$FILE" ] then echo "and it is empty" else echo "and it is not empty" fi exit 0 |
Tipo di File |
L'esempio proposto utilizza il comando test <expr>, nella forma [ <expr> ], per "leggere" i flag di stato dei file.