previous up

Introduzione allo Shell Scripting

index next

Subsections



5. Esempi avanzati

In questo capitolo saranno raccolti degli script più complicati (niente paura, nulla di difficle), vicini alla realtà di un utente e pertanto utili. Eventualmente, si troveranno contributi da parte degli utenti, i quali possono inviare i propri script con un breve commento.




Gli esempi saranno divisi per argomento ed i commenti saranno, per la maggior parte, all'interno del codice stesso.


5.1 Script d'amministrazione


5.2 Utilità

In questa sezione verranno riportati alcuni script utili nell'uso quotidiano.

5.2.1 Esempio 5.2.1: Creare un cgi con la shell

Negli ultimi anni sono state sviluppate nuove tecnologie e nuovi linguaggi per semplificare la vita a chi scrive cgi (Common Gateway Interface, volgarmente: pagine web a contenuto dinamico). Creare un cgi con Bash può pertanto sembrare una forzatura, dato che questa non è la scelta tecnica migliore, ciò nonostante tratteremo questo argomento perché ricco dal punto di vista didattico.




Per semplicità, tratteremo in dettaglio soltanto il metodo GET del protocollo HTTP, POST sarà accennato verso la fine.




Per poter eseguire con successo lo script, occorrerà avere un server web (ad esempio Apache, http://httpd.apache.org) funzionante, configurato per supportare l'esecuzione di cgi.




Utilizzando il metodo GET, le ``coppie'' \fbox{\texttt{variabile=valore}} vengono passate attraverso l'url. Ad esempio spesso vediamo url del tipo :
\fbox{\texttt{http://www.example.com/cgi-bin/script?var1=ciao\&var2=bau}}
Vediamo dunque che le coppie variabili=valore sono separate l'una dall'altra attraverso il carattere \fbox{\texttt{\&}}, mentre sono separate dal percorso dello script attraverso \fbox{\texttt{?}}.

Figura 5.1: Url con il metodo GET
\includegraphics[scale=0.9]{fig/get}
Il server web mette a disposizione la stringa a destra del carattere \fbox{\texttt{?}} in un'apposita variabile d'ambiente, $QUERY_STRING. Per ottenere le coppie variabile=valore sarà dunque sufficiente analizzare il contenuto di tale stringa.




Vediamo nella pratica come realizzare lo script.

#!/bin/bash
#
# Esempio di cgi con bash

EXIT_SUCCESS=0

# dichiariamo un vettore in cui salvare i valori delle variabili
# passate con GET

declare -a QUERY


# Una funzione attraverso la quale impostare la parte di testa
# della pagina. La stringa "Content-type: text/html" serve ad istruire
# il browser ad interpretare quanto segue come html. Se avessimo,
# invece scritto "Content-type: text/plain" otterremmo la pagina
# senza che il codice html venga interpretato.
# 
# La funzione accetta un argomento, il titolo della pagina.

sopra () {
    
    cat <<EOF
Content-type: text/html

<HTML>
<HEAD>
  <TITLE>$1</TITLE>
</HEAD>
<BODY>
<!-- fine parte superiore -->

EOF

}


# Con questa funzione, invece, definiamo gli elementi comuni della
# parte inferiore della pagina, in modo da chiuderla correttamente.

sotto () {
    
    cat <<EOF

<!-- inizio parte inferiore -->
</BODY>
</HTML>
EOF

}


# La funzione che segue è il "cuore" dello script, grazie a questa
# possiamo analizzare il contenuto di $QUERY_STRING, ottenere il
# valore di ogni variabile e salvarlo in un elemento del vettore
# $QUERY.
#
# Si noti il particolare modo in cui abbiamo iterato con il ciclo
# while; abbiamo dovuto "sprecare" il primo elemento del vettore. Se
# non avessimo agito in questo modo, infatti, la condizione valutata
# dal ciclo sarebbe stata falsa alla prima esecuzione e non avremmo
# mai ottenuto le variabili che ci interessano. Questo artificio
# simula in qualche modo il ciclo "do ... while" presente in altri
# linguaggi quali ad esempio il C.

analizza_query () {

    i=0
    QUERY[0]="OK"

    while [ -n "${QUERY[${i}]}" ]; do
        # Aumentiamo subito il valore di $i
        ((i=$i+1))
        # Otteniamo il valore delle variabili
        QUERY[${i}]="$(echo $QUERY_STRING | cut -d \& -f ${i} \
            | cut -d = -f 2 | sed 's/+/ /g')"
    done

}


# Questa funzione ci consente di ottenere una form attraverso la quale
# ottenere un input dall'esterno.

prima_pagina () {

    cat <<EOF
<P>
  Inserisci nome e cognome:
</P>

<FORM method="GET" action="$(basename $0)">
  <P>
    <B>Nome</B>:<BR> <INPUT type="TEXT" name="nome">
  </P>
  <P>
    <B>Cognome</B>:<BR> <INPUT type="TEXT" name="cognome">
  </P>
  <INPUT type="SUBMIT">
</FORM>
EOF

}


# Con questa funzione mostriamo i valori ottenuti dall'esterno.

seconda_pagina () {
    cat <<EOF
<H2>
  Ciao ${QUERY[1]} ${QUERY[2]}!
</H2>

<P>
  Hai visto quanto è divertente fare cgi con bash?
</P>

<P>
  <A href="$(basename $0)">Torna alla pagina precedente</A>
</P>
EOF

}

# FINE delle funzioni

analizza_query

if [ -z "${QUERY[1]}" ]; then
    sopra "Inserisci nome e cognome"
    prima_pagina
else
    sopra "Risultato"
    seconda_pagina
fi

sotto
    
exit $EXIT_SUCCESS

Come funziona:

Analizzeremo principalmente parte dellla funzione analizza_query() ed il codice al di sotto di # FINE delle funzioni.




Procediamo con ordine ed occupiamoci in particolare della stringa di codice

QUERY[${i}]="$(echo $QUERY_STRING | cut -d \& -f ${i} \
            | cut -d = -f 2 | sed 's/+/ /g')"
presente all'interto di analizza_query(). QUERY[${i}] è un elemento di un vettore, mentre la stringa di desta è il risultato di una serie di pipe di alcuni comandi,
echo $QUERY_STRING | cut -d \& -f ${i} \
            | cut -d = -f 2 | sed 's/+/ /g'}
Analizziamoli uno ad uno. Il primo, echo $QUERY_STRING, stampa sullo standard output il contenuto della variabile $QUERY_STRING che, grazie alla pipe, diventa standard input di cut -d $\backslash$& -f ${i}. In generale, cut è un comando che serve ad eseguire operazioni avanzate di estrazione di testo da una stringa; in particolare, con la sintassi utilizzata, diciamo a cut di dividere la stringa in campi (Utilizzando come separatore &, che deve essere preceduto da un backslash per evitare che la shell lo interpreti come istruzione) ed estrarne quello di posto ${i}. In questo modo, otteniamo la coppia variabile=valore di posto ${i}.




Il secondo cut è del tutto analogo al primo, tuttavia questa volta si usa come separatore di campi il carattere = (Non ha bisogno di alcun escape) e se ne ritorna il campo di posto 2; così facendo, otteniamo il valore della variabile in esame.




In ultimo, dobbiamo tener presente il fatto che ogni spazio presente nel valore di una variabile viene sostituito con un +, pertanto, quando ritorniamo i valori dobbiamo ricordarci di risostituire ogni occorrenza di + con uno spazio. Questa operazione viene effettuata attraverso il comando \fbox{\texttt{sed 's/+/ /g'}}.




Passiamo ora ad analizzare il resto. Dopo aver dichiarato tutte le funzioni necessarie, inizia la parte dello script che prende le decisioni. Innanzi tutto, lanciamo la funzione analizza_query() e decidiamo cosa fare a seconda del suo operato, infatti controlliamo l'elemento ${QUERY[1]}, se il suo valore non è impostato, allora lo script è stato richiamato senza passare alcun paramentro nell'url, dunque eseguiamo le funzioni sopra() (Passandole come argomento la stringa ``Inserisci nome e cognome'') e prima_pagina() per raccogliere i dati.




Se, invece, il valore di ${QUERY[1]} fosse impostato, allora lo script sarebbe stato richiamato passandogli dei parametri, quindi si richiamerebbero le funzioni sopra() (Argomento ``Risultato'') e seconda_pagina() in modo da mostrare i valori raccolti.




In ultimo, viene richiamata la funzione sotto() per chiudere correttamente il codice html della pagina e si ritorna un valore di successo.


5.3 One-line

Con one-line intendiamo dei semplici script che si sviluppano tutti su una sola linea di codice, utili spesso per portare a termine in un batter d'occhio i lavori sporchi.

5.3.1 Esempio 5.3.1: terminare un processo per nome

Può capitare, ad esempio, di avere a che fare con programmi che non rispondono più ai comandi (raramente) ed occorre dunque terminarli. Andare alla ricerca del PID (Process ID) del processo per poi mandare al programma un segnale di KILL può essere fastidioso, sarebbe più bello se, magari, potessimo fare tutto fornendo il solo nome del programma. Siamo fortunati, leggendo la pagina manuale di ps, possiamo notare l'esistenza dell'opzione -C (Controllate!) che fa proprio al caso nostro. Vediamo, dunque, come fare.

$ kill -9 $(ps -C nome_comando -o pid=)

o

$ kill -KILL $(ps -C nome_comando -o pid=)

Come funziona:

Analizziamo passo passo ciò che abbiamo scritto. Il comando kill serve ad inviare segnali ai processi; nel nostro caso, stiamo inviando il segnale di KILL (terminazione) attraverso l'opzione -9 5.1. Tale comando necessita come input del PID del processo in questione, per questo motivo abbiamo utilizzato l'espanzione di comandi $( ... ) su \fbox{\texttt{ps -C
nome\_comando -o pid=}}, con le opzioni specificate, infatti, ps dà come output il pid5.2del processo lanciato da nome_comando5.3. Per concludere, se volessimo terminare il processo lanciato dal programma gabber5.4 dovremmo digitare i comandi:

$ kill -9 $(ps -C gabber -o pid=)

previous up

Introduzione allo Shell Scripting

index next

Domenico Delle Side 2002-09-24