Programmare con la shell Bash impone necessariamente la conoscenza della sintassi del linguaggio impiegato ma, questa, non è certamente la difficoltà maggiore cui si va incontro. In effetti, la sintassi di Bash è abbastanza ricca da mostrare una potenza espressiva simile a molti linguaggi di programmazione strutturata di ultima generazione. Esiste, infatti, un impiego criptico di alcuni simboli di controllo, in parte ereditato dal C, utili a velocizzare l'implementazione dello script e dargli una forma particolarmente compatta. L'acquisizione delle necessarie abilità implementative è frutto della pratica programmativa con questo linguaggio cosicchè, nel seguito, vengono proposti e discussi alcuni esempi, anche rivisitando esempi già visti.
Nel seguito vengono proposti alcuni esempi ragionati di shell script per la shell Bash. Si è volutamente cercato di utilizzare il più possibile la sintassi della Bourne shell che, come è noto, costituisce un sottoinsieme proprio della shell Bash. Le caratteristiche salienti sono l'utilizzo di parametri sia posizionali, per acquisire i valori passati dalla shell chiamante, sia variabile, per immagazzinare risultati intermedi dell'elaborazione. Vengono impiegati, inoltre, il ciclo for e il salto condizionato if-then-else. Altre caratteristiche impiegato sono il quoting e il back-quoting, quest'ultimo necessario quando si vuol assegnare il risultato di un comando, prodotto su stdout, ad un parametro variabile. Vengono utilizzati, inoltre, alcuni comandi di utilità generale il cui impiego e spiegazione dettagliata possono ottenersi consultando il manuale in linea con man <nomecomando>.
In questo esempio la determinazione dell'identificatore di processo PID, associato all'esecuzione del programma <progname> viene ottenuta analizzando opportunamente le informazioni prodotte in uscita dal comando ps che legge la tabella contenente i descrittori PCB dei processi correntemente attivi. L'analisi viene demandata al programma grep la cui azione è quella di estrarre, da un dato file di testo, tutte le righe che contengono un certo pattern passato come primo argomento.
L'impiego della pipe permette di trasferire l'output di ps a grep; il secondo filtraggio serve ad eliminare la riga che contiene il nome del processo passato come parametro a grep stesso. Infine, con il back-quote si assegna la riga cercata alla variabile A la quale, prima dell'assegnazione, viene trasformata in una lista di parole, interpretando il blank come separatore.
Si tenga presente che, in generale, è possibile che siano presenti più processi lanciati con lo stesso programma cosicchè A si presenta come un array di stringhe. Il ciclo for serve a scorrere questa lista e, nel caso specifico, ad estrarre il primo elemento, mediante il contatore K, che è proprio il primo PID cercato.
#!/bin/bash # Synopsis: fpid <progname> # A=`ps ax | grep -e $1 | grep -v grep` ; if [ "$A" == "" ] then echo "no such a process" ; exit 1 ; fi K=1 for ITEM in $A do if [ $K -eq 1 ] then echo $ITEM ; fi K=`expr $K + 1` ; done exit 0 |
find the first running program |
In questo secondo esempio si procede in modo analogo al precedente con un ulteriore filtraggio per contare il numero di righe contenenti il nome del processo cercato; il comando utilizzato è wc con l'opzione -l. L'estrazione dei PID richiesti, su cui eseguire la terminazione forzata con il comando kill -9, è ottenuta dal ciclo while che scorre la lista TPID con l'avvertenza che il numero di item per ogni processo trovato è 4, conseguenza del fatto che il comando ps è stato eseguito con l'ozione -e.
Si noti, anche, come in questo caso venga utilizzato nel ciclo il comando read che serve a leggere la stringa introdotta dall'utente in risposta alla richiesta proposta col comando echo -n, in cui il flag -n notifica al comando di non avanzare alla riga successiva.
#!/bin/bash # gkill : terminazione selettiva dei processi # Synopsis : gkill <progname> if [ "$#" -eq 0 ] then echo "Usage: gkill <progname>" exit 1 fi PROG=$1 declare -a TPID declare -i N N=`ps -e | egrep -e " $PROG" | grep -v "grep" | wc -l` if [ $N -eq 0 ] then echo "No such a process" exit 2 fi TPID=(`ps -e | egrep -e " $PROG" | grep -v "grep"`) J=0; K=$N; while [ "$K" -gt 0 ] do echo -n "Vuoi terminare ${TPID[$J]}?(y/n) " read YES if [ "$YES" == "y" ] then kill -9 "${TPID[$J]}" fi J=`expr $J + 4` K=`expr $K - 1` done exit 0 |
Terminazione selettiva dei processi |
Il seguente programma ricerca, a partire da una lista di directory, quale di queste contiene il comando il cui nome è passato come argomento. Per effettuare la ricerca si analizza il contenuto della variabile d'ambiente PATH nella quale sono registrate tutti i possibili percorsi che raggiungono le directory in questione. Il programma si basa sulla particolare codifica utilizzata in questo caso, secondo la quale la separazione fra una directory e l'altra è ottenuta con il carattere :. Dunque, utilizzando il comando sed, viene effettuata una trasformazione della stringa PATH, sostituendo (comando s) tutte (flag g) le occorrenze di : con un blank.
La nuova stringa SEARCH_PATH è costituita da una serie di item che rappresentano directory per cui scorrendo la lista con la variabile DIR si verifica se "$DIR/$1" è il comando in questione che si trova nella directory correntemente analizzata.
#! /bin/sh # where cmd : where is (the path of) # the cmd command # CURR_PATH=$PATH PATH=/bin:/usr/bin if [ "$#" -eq 0 ] then echo 'Usage: where command' ; exit 2 fi SEARCH_PATH="`echo ${CURR_PATH} | sed 's/:/ /g'`" K=0 for DIR in ${SEARCH_PATH} do if [ -f ${DIR}/$1 ] then echo ${DIR}/$1 K=`expr $K + 1` fi done if [ "$K" -eq 0 ] then echo "Command not found" exit 1 else exit 0 fi |
Dislocazione dei comandi |
Nel semplice esempio che segue viene messo in evidenza il ruolo posizionale dei parametri utilizzati per associare il valore degli argomenti passati dalla shell chiamante al comando da eseguire.
#!/bin/bash # wh-trans : Translate a sentence in a query form # Synopsis : wh-trans |
Trasformazione in forma interrogativa |
Nell'esempio proposto viene implementato un comando di cancellazione controllata di file e directory il cui effetto è di spostare tali file nella directory ~/.wastebasket che, per questo funge da cestino. Nella prima parte del comando viene fatto il parsing degli argomenti, distinguendo fra opzioni e argomenti, e che vengono posti in liste separate, contandone il numero per i secondi.
Superato il test di consistenza, si verifica se è presente il flag v che non può essere passato al comando mv e, perciò richiede un trattamento ad hoc. Dunque, mediante ciclo for si passa all'eliminazione selettiva dei file indicati, eventualmente richiedendo conferma all'utente.
#!/bin/bash # 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 if [ ! -d "$HOME/.wastebasket" ] then mkdir "$HOME/.wastebasket" fi for NAME in $NAMES do if [ $VERBOSE -eq 0 ] > /dev/null; then mv $OPT $NAME $HOME/.wastebasket else echo -n "delete $NAME (y/n) " read YES if [ $YES = "y" ] > /dev/null; then mv $OPT $NAME $HOME/.wastebasket fi fi done exit 0 |
Cancellazione sicura di file |