LEZIONE del 8 Novembre

Alcune caratteristiche fondamentali della shell, come la descrizione dei comandi predefiniti e la realizzazione dei comandi composti, sono già state trattate nelle precedenti lezioni. Per avere una descrizione dettagliata di ciascun comando, costrutto linguistico, variabile d'ambiente, interpretazione dei simboli speciali, sostituzione delle variabili, o altro si consiglia di consultare il manuale in linea della shell.

Una discussione ragionata sull'impiego della shell è disponibile al link indicato dove, fra l'altro, si può consultare una larga raccolta di esempi d'uso della shell, assai utili nell'implementazione di script. Nel seguito verranno proposti alcuni esempi che si riferiscono alle capacità di calcolo aritmetico fornito dalla shell.

Relativamente all'eseguibilità di comandi in modalità remota la shell offre diverse possibilità a seconda del tipo di connessione realizzata con il server. Un esempio interessante a questo riguardo è quello relativo all'interazione client-server basata sulla richiesta di pagine HTML. L'argomento è assai complesso, dovuto anche allo sviluppo imponente di questo metodo in seguito alla diffusione dei webserver sulla rete internet. Nel seguito verrà considerata un'estensione delle richieste di pagina tale da permettere al browser di inoltrare l'esecuzione di una procedura remota fornendo gli appropriati parametri.

ESECUZIONE REMOTA DI SHELL SCRIPT

La possibilità di eseguire comandi in modalità remota è uno degli aspetti più interessanti che le reti di calcolatori mettono a disposizione dell'utente finale. Qualunque sia lo schema utilizzato per implementare una funzionalità di questo tipo, è sempre necessario disporre di un meccanismo di trasporto bidirezionale della richiesta di esecuzione di un'azione con gli eventuali parametri qualificanti, dal cliente locale verso il server remoto per ricevere, successivamente, da quest'ultimo il risultato della sua effettiva esecuzione remota.

Esiste, a questo proposito, uno specifico protocollo che permette di attivare l'azione con un meccanismo noto come chiamata di procedura remota RPC, analogo alle usuali chiamate di procedura dei linguaggi di programmazione sequenziali. Nella pratica, tuttavia, il protocollo RPC è utilizzato quasi esclusivamente per implementare i file system di rete locale NFS sui quali è possibile effettuare un ragionevole controllo di sicurezza, altrimenti improponibile per altri tipi di applicazioni.

I primi meccanismi di esecuzione remota erano basati esclusivamente sull'accesso da un sistema locale verso un server remoto di login per utenti abilitati ad utilizzare spazio disco, tempo di CPU e memoria di lavoro per mezzo del protocollo TELNET, il cui scopo era quello di mettere a disposizione dell'utente remoto una sorta di terminale virtuale su cui lavorare.

La situazione odierna è assai diversa perchè gli utenti non necessitano l'esecuzione remota di un qualsivoglia comando ma, piuttosto, sono indotti ad utilizzare un numero limitato di comandi pressochè uniforme su molti calcolatori della rete.

E' noto, a questo proposito, come il protocollo HTTP permetta ad un qualunque browser di fare una richiesta di pagina verso un server remoto, il cosiddetto webserver, per ricevere da quest'ultimo la pagina indicata e quindi visualizzarla opportunamente secondo le direttive specificate dall'HTML, il linguaggio nel quale sono codificate le informazioni della pagina in questione.

L'azione base implementata dal browser è definita nel modo seguente

get <prot>://<host>.<domain>/<dirpath>/<page>

la cui esecuzione ha il duplice effetto di individuare sulla rete il webserver di indirizzo <host>.<domain> e su questo, ricercare la pagina <page> nella directory <dirpath> per trasferirla al client che ne aveva fatto richiesta, secondo le modalità codificate in <prot>.

Come è noto, il parametro associato al comando GET prende il nome di url, Uniform Resource Locator, e contiene tutte le informazioni necessarie ad effettuare il trasferimento di pagina verso il browser che ne aveva fatto richiesta.

AZIONE REMOTA

Originariamente le pagine dei webserver erano organizzate come quelle di una qualunque enciclopedia tascabile con una serie di riferimenti ad altre pagine, che potevano essere situate su server diversi, contenenti testo ed immagini, strutturate in modo ipertestuale, per mezzo di specifici TAG del linguaggio HTML.

Con lo sviluppo di internet si è passati ad una organizzazione multimediale dell'informazione, da cui la necessità da parte dei server di strutturare le informazioni utilizzando i cosiddetti MIME format e da parte dei client di disporre degli opportuni plug-in, specifici moduli del browser, capaci di dar forma alle informazioni secondo le specifiche codificate dal MIME format.

Per permettere l'interazione attiva client/server si è passati ad una estensione del protocollo HTTP in modo che questo potesse permettere l'esecuzione di azioni arbitrariamente definite nelle pagine stesse. In questo modo il contenuto di una pagina, oltre ad essere semplice informazione può contenere codice eseguibile sia sul lato server che sul lato client. Tipicamente JAVA e JAVASCRIPT sono utilizzati come linguaggi di codifica delle azioni sul lato client, mentre SH, BASH, CSH, PERL, PHP sono linguaggi utilizzati per la codifica delle azioni sul lato server. Si tenga presente, comunque, che la distinzione dei linguaggi per il lato server e il lato client è del tutto indicativa.

Nel seguito faremo riferimento alla possibilità di eseguire azioni sul lato server come sequenze di comandi codificate in shell script per mezzo di un meccanismo generale di interfacciamento noto come CGI, Common Gateway Interface, implementato come un estensione del protocollo HTTP in cui l'URL associato al comando GET viene generalizzato nel modo seguente

get http://host.domain/dirpath/cmd.cgi?parm1=val1&...&parmK=valK

dove cmd è il comando che si vuole far eseguire dal server ed è codificato nella grammatica della shell ed interfacciato con il protocollo HTTP mediante il protocollo CGI. Quest'ultimo stabilisce in che modo devono essere passati i parametri provenienti dal browser verso la shell che esegue l'azione interpretando lo shell script.

Tale meccanismo di interfacciamento ed invocazione dell'azione è realizzato da due pagine distinte, che chiameremo cmd.html e cmd.cgi, rispettivamente, se cmd è il comando che si vuole implementare.

Nella prima pagina, quella in formato HTML, si prepara la richiesta di invocazione del comando con l'utilizzo del tag FORM, secondo lo schema riportato di seguito nel quale, tuttavia, si possono organizzare le informazioni utilizzando costrutti diversi dal costruttore di lista UL come, ad esempio, le tabelle TABLE, ecc...

<HTML>
.........
<HR>
<FORM NAME=... ACTION="/dirpath/cmd.cgi" METHOD=post>
<UL>
<LI> parm1 : <INPUT TYPE=text NAME=parm1 SIZE=... MAXLENGTH=...>
<LI> parm2 : <INPUT TYPE=text NAME=parm2 SIZE=... MAXLENGTH=...>
.........
<LI> parmK : <INPUT TYPE=text NAME=parm2 SIZE=... MAXLENGTH=...>
</UL>
<HR>
<INPUT TYPE="submit" NAME=ACTION VALUE=opt1>
<INPUT TYPE="submit" NAME=ACTION VALUE=opt2>
.........
<INPUT TYPE="reset" VALUE=clear>
</FORM>
.........
</HTML>

Richiesta dei Parametri mediante Form

In particolare, nella precedente si è supposto che il comando sia eseguibile con un certo numero di opzioni mutuamente esclusive, per ciascuna delle quali è associato un tasto da attivare selezionandolo. Questa pagina viene trasferita dal server verso il client con il comando

get http://host.domain/dirpath/cmd.html

secondo quanto si è già visto per il normale funzionamento del protocollo HTTP. A questo punto la pagina viene processata localmente e istanziata la richiesta di esecuzione dello shell script con i parametri passati nel modo indicato.

Questa seconda pagina, infatti, non viene trasferita dal server verso il client ma viene eseguita, in virtù dell'interfacciamento realizzato dal CGI che permette di trasferire i parametri verso la shell. Il passaggio dei parametri avviene secondo le caratteristiche indicate dal metodo, che appare come una delle proprietà obbligatorie della FORM. I due possibili metodi sono get e post, il secondo da preferirsi al primo.

Se si utilizza il server Roxen, sviluppato al CERN di Ginevra, lo schema generale della pagina è il seguente

#!/bin/bash
#
echo Content-type: text/plain   # oppure  text/html
..............
..............
<definizioni generali di variabili>
.............
.............
# Common Gateway Interface location
#
CGI="/<dirpath>/cgiparse"
#
.............
.............
# initialize cgiparse to process parameters filled in by HTML
#
eval `$CGI -init`
eval `$CGI -form`
#
..............
..............

Azione eseguita mediante CGI su Roxen

Nel caso, invece, del più comune server Apache non solo utilizzato in ambiente Linux, lo schema generale della pagina è quello riportato di seguito

#!/bin/bash
echo Content-type: text/plain   # oppure  text/html
#
# Common Gateway Interface: parsing
#
function Get_arg() {
  echo "$QUERY_STRING" | tr '&' '\n' | grep "^$1=" | head -1 | sed "s/.*=//"
}
#
..............
<definizioni generali di variabili>
.............
# Common Gateway Interface: acquiring parameters filled in by HTML
#
if [ "$REQUEST_METHOD" = POST ]; then
  QUERY_STRING=$( head --bytes="$CONTENT_LENGTH" );

parm1=$(Get_arg "parm1" );
parm2=$(Get_arg "parm2" );
..............

Azione eseguita mediante CGI su Apache

L'eventuale output prodotto dall'esecuzione dello script viene ritrasferito al browser secondo le modalità indicate dall'istruzione iniziale dello script e che può essere

echo "Content-type: text/plain"
echo

qualora la sequenza dei caratteri è presa letteralmente per quello che è, oppure,

echo "Content-type: text/html"
echo

se l'output prodotto deve intendersi come una nuova pagina HTML e, perciò, generata dinamicamente dallo script.

Esempio: Rubrica Telefonica

A scopo esemplificativo si consideri il caso di un web server che contenga indirizzi e numeri telefonici degli utenti che sono autorizzati ad accedervi e che sia modificabile, ossia si possano aggiungere o togliere indirizzi, e consultabile.

Ovviamente le informazioni saranno contenute in una pagina non direttamente accessibile e queste potranno essere consultate e modificate tramite azioni remote invocate dall'esecuzione di uno script. Specificatamente, illustriamo nel seguito il contenuto delle due pagine consult.html e consult.cgi che realizzano le operazioni richieste secondo quanto si è detto in precedenza

La pagina HTML implementa l'interfaccia fra il protocollo HTTP e la SHELL che esegue lo script. Nel caso in esame assume la forma seguente

<HTML>
<HEAD> <TITLE> Phone Book </TITLE></HEAD>
<BODY>
<H1> Phone Book </H1>
<HR>
<FORM NAME="phonebook" ACTION="consult.cgi" METHOD=POST>
<UL>
<LI>
Name : <INPUT TYPE="text" NAME=Name SIZE=20 MAXLENGTH=20>
Cognome : <INPUT TYPE="text" NAME=Surname SIZE=40 MAXLENGTH=40>
<LI>
Via : <INPUT TYPE="text" NAME=Street SIZE=25 MAXLENGTH=25>
Numero : <INPUT TYPE="text" NAME=Number SIZE=6 MAXLENGTH=6>
Città : <INPUT TYPE="text" NAME=City SIZE=20 MAXLENGTH=20>
<LI>
Prefix : <INPUT TYPE="text" NAME=Code SIZE=4 MAXLENGTH=4>
Phone : <INPUT TYPE="text" NAME=Phone SIZE=10 MAXLENGTH=10>
</UL>
<P>
<INPUT TYPE="submit" NAME=Action VALUE=add>
<INPUT TYPE="submit" NAME=Action VALUE=delete>
<INPUT TYPE="submit" NAME=Action VALUE=search>
<INPUT TYPE="reset" VALUE=clear>
</FORM>
<HR>
</BODY>
</HTML>

Esempio della Rubrica Telefonica

e quando viene trasferita dal server al browser si presenta secondo le caratteristiche specifiche di rendering dello stesso.

Per quanto riguarda, invece, lo script consult.cgi questo è strutturato nel modo seguente

#!/bin/bash
#
echo "Content-type: text/plain"
echo
date
echo
#
# script handling phonebook entries
#
WHERE="$HOME/dirpath1"
#
# Common Gateway Interface location
#
CGI="$WHERE/examples/cgiparse"
#
DATABASE="$WHERE/examples/phonebook"
TMP=$DATABASE/TMP$$
PHONEBOOK=$DATABASE/info.db

# initialize cgiparse to process parameters filled in by HTML
#
eval `$CGI -init`
eval `$CGI -form`
#
# Now the environment variable QUERY_STRING holds the input entered by
# the Web, with CONTENT_LENGTH its length
#

if [ "$FORM_Surname" ] > /dev/null &&
   [ "$FORM_Name" ] > /dev/null
then
   case $FORM_action in
      add)  # add a new entry in the phonebook
         if [ "$FORM_Code" ] > /dev/null &&
            [ "$FORM_Phone" ] > /dev/null
         then
            INFO1="$FORM_Surname:$FORM_Name:($FORM_Code)$FORM_Phone"
            INFO2="$FORM_Street:$FORM_Number:$FORM_City"
            INFO="$INFO1:$INFO2"
            echo "$INFO" >>  $TMP;
            sort +0 $TMP >>  $PHONEBOOK
            /bin/rm -f $TMP
         fi ;;
      delete)  # delete a selected entry in the phonebook
         if [ `grep -e "$FORM_Surname:$FORM_Name"  $PHONEBOOK` ] > /dev/null
         then
           sed -e '/${FORM_Surname}:${FORM_Name}:/d' $PHONEBOOK ;
           echo "The entry has been deleted"
         fi
         ;;
      search)  # search address and phone number in the phonebook
         INFO=`grep -e "$FORM_Surname:$FORM_Name" $PHONEBOOK` ;
         echo $INFO
         ;;
   esac
fi
exit 0

Esempio di file CGI

Nell'esempio proposto l'implementazione dell'azione eseguita sul lato server è quella richiesta dal server Roxen. Come si è visto, nel caso si utilizzi il server Apache , si deve procedere in maniera leggermente diversa. Tale differenza dipende dal meccanismo con il quale i due server acquisiscono i parametri dalle pagine HTML. Nel seguito viene proposta una semplice pagina CGI per verificare i parametri ottenuti dal Web.

#!/bin/bash
echo "Content-type: text/plain"
echo

  function Get_arg () {
    echo "$QUERY_STRING" | tr '&' '\n' | grep "^$1=" | head -1 \
                         | sed "s/.*=//"
  }

  if [ "$REQUEST_METHOD" = POST ]; then
    QUERY_STRING=$( head --bytes="$CONTENT_LENGTH" );
  fi

  FORM_Surname=`Get_arg "Surname"`
  FORM_Name=`Get_arg "Name"`
  FORM_Street=`Get_arg "Street"`
  FORM_Number=`Get_arg "Number"`
  FORM_City=`Get_arg "City"`
  FORM_Code=`Get_arg "Code"`
  FORM_Phone=`Get_arg "Phone"`
  FORM_Action=`Get_arg "Action"`

  echo "QUERY_STRING[$CONTENT_LENGTH]:"
  echo "        $QUERY_STRING"
  echo
  echo "Surname=$FORM_Surname"
  echo "Name=$FORM_Name"
  echo "Street=$FORM_Street"
  echo "Number=$FORM_Number"
  echo "City=$FORM_City"
  echo "Code=$FORM_Code"
  echo "Phone=$FORM_Phone"
  echo "Action=$FORM_Action"
exit 0

Tracciamento dei Parametri acquisiti

La realizzazione di script cgi mediante la shell Bash segue la falsa riga dello schema già visto per gli script Bash lanciati dalla shell locale. L'unica reale difficoltà deriva dalle restrizioni al debugging quando si implementano script remoti. Si può ovviare parzialmente a tali restrizioni consultando gli error_log prodotti dal server Apache.

Infine, sono necessarie alcune note sulla configurazione del server Apache affinchè questo possa eseguire script remoti. Si tratta, essenzialmente, di modificare il file di configurazione httpd.conf che si trova in /etc/httpd/conf modificando il modulo IfModule mod_userdir.c in modo da consentire al server l'accesso a pagine che non si trovano sotto la radice /var/www/html ed eseguire "pagine" che si trovano sotto /var/www/cgi-bin. Si può, ad esempio, apportare la seguente modifica

<IfModule mod_userdir.c>
    #
    # UserDir is disabled by default since it can confirm the presence
    # of a username on the system (depending on home directory
    # permissions).
    #
    # UserDir disable

    #
    # To enable requests to /~username/ to serve the user's public_html
    # directory, remove the "UserDir disable" line above, and uncomment
    # the following line instead:
    # 
    UserDir public_html

    <Directory /home/username/public_html>
       Options +ExecCGI
       Allow from all
    </Directory> 
    AddHandler cgi-script .cgi .pl
</IfModule>

Modica della Configurazione del server Apache

cosicchè l'utente username può pubblicare sul "web" la directory public_html, ma può essere scelta qualunque altra, purchè il suo contenuto sia accessibile in lettura a tutti. Lo stesso dicasi per i file .cgi e .pl purchè siano leggibili ed eseguibili da tutti. Naturalmente è necessario che il firewall sia aperto sulla porta 80 ed, inoltre, il webserver httpd deve essere in esecuzione.

GRAMMATICA DELLA SHELL

Si è già visto come sia possibile eseguire valutazioni di espressioni aritmetiche e/o logiche da parte dell'interprete della shell, chiamando il subinterprete expr il quale, al termine dell'esecuzione restituisce il valore dell'espressione passata come parametro. Cpme è già stato evidenziato, è possibile utilizzare anche il comando let; in questo caso la shell, anzichè lanciare un processo, procede alla chiamata della routine che implementa il comando in questione.

Nel seguito sono riportati gli operatori che la shell mette a disposizione per costruire le espressioni aritmetiche e logiche, quest'ultime potendo essere impiegate all'interno dello speciale comando test per la valutazione delle espressioni condizionali che compaiono in alcuni comandi composti. La lista completa dei comandi predefiniti è già stata discussa nelle lezioni precedenti.

Calcolo Aritmetico

La shell permette di calcolare espressioni aritmetiche, sotto certe circostanze. La valutazione viene fatta in interi long senza controllo per l'overflow, benchè la divisione per 0 sia intercettata e segnalata di come errore. La seguente lista di operatori è raggruppata per operatori uguale livello di precedenza. I livelli sono elencati in ordine decrescente di precedenza.

Ovviamente, gli operandi possono contenere variabili di shell; l'espansione di parametro viene effettuata prima che l'espressione sia valutata. Il valore di un parametro dentro una espressione è forzato a un intero long. Una variabile di shell non ha bisogno di aver assegnato il suo attributo intero per poter essere impiegata in una espressione.

Le constanti con uno 0 iniziale sono interpretate come numeri ottali. Uno 0x o 0X iniziale indica numeri esadecimali. Altrimenti, i numeri prendono la forma [base#]n, dove base è un numero decimale tra 2 e 36 che rappresenta la base aritmetica, e n è un numero in quella base. Se base è omessa, allora è usata la base 10.

Gli operatori sono valutati in ordine di precedenza. Le sottoespressioni tra parentesi sono valutate prima e possono superare le regole di precedenza di cui sopra.

Nell'esempio proposto di seguito vengono utilizzati alcuni degli operatori aritmetici e logici appena discussi. Va ricordato che il calcolo aritmetico gestito direttamente dalla shell è limitato al solo uso dei numeri interi. Quando la shell analizza una stringa di soli caratteri numerici la converte automaticamente in un numero intero di 4 byte (long integer). Se, invece, nella stringa numerica compare anche il punto (che sta a significare valore reale o floating point) non viene eseguita nessuna conversione. L'ultimo degli esercizi proposti cerca di gestire tale limitazione associando una coppia di interi per rappresentare la parte intera e la parte decimale del numero reale. L'effetto è che l'implementazione delle usuali operazioni aritmetiche diventa più complessa.

Si noti come in tutti gli esempi la valutazione delle espressioni aritmetiche cade sotto il controllo del comando let che, come si è visto, è uno dei comandi predefiniti della shell e, per questo, viene lanciato come routine interna della shell.

#!/bin/bash
#  Synopsis: arith 
#
if [ "$#" -eq 0 ]; then
  echo "Usage: arith "
  exit 1
fi

PROMPT="? "

  function arith1() {
    read -p "$PROMPT"
    N="$REPLY"
    K=0;
    A=0;
    while [ "$K" -le "$N" ]; do
      let "A = $A + $K";
      let "K = $K + 1" ;
    done
    echo "$A"
    return 1
  }

  function arith2() {
    read -p "$PROMPT"
    N="$REPLY"
    K=1;
    A=1;
    while [ "$K" -le "$N" ]; do
      let "A = $A * $K";
      let "K = $K + 1" ;
    done
    echo "$A"
    return 2
  }

  function arith3() {
    read -p "$PROMPT"
    N="$REPLY"
    read -p "$PROMPT"
    M="$REPLY"
    let "Q = $N / $M" ;
    let "R = $N % $M" ;
    echo "$N = ${Q}*${M} + $R"
    return 3
  }

  function arith4() {
    read -p "$PROMPT"
    X="$REPLY"
    read -p "$PROMPT"
    N="$REPLY"
    A=(`echo $X | sed "s/\./ /"`)
    M=${A[1]}; L=${#M};
    let "P = $N * ${A[0]}"
    let "Q = $N * ${A[1]}"
    E="1:10:100:1000:10000:100000"
    Dx=(`echo $E | sed 's/:/ /g'`)
    D=${Dx[$L]}
    let "P = P + Q / $D";
    let "Q = Q % $D" ;
    echo "${P}.${Q}"
    return 4
  }

case $1 in
  1) arith1 ;;
  2) arith2 ;;
  3) arith3 ;;
  4) arith4 ;;
esac

echo "Action $? has been executed"
exit 0

Esempi d'uso degli operatori aritmetici