LEZIONE del 26 Ottobre

Nella precedente lezione sono stati definiti gli elementi base della sintassi Bash, con l'ausilio dei quali vengono manipolati i parametri e costruiti i comandi composti. Questi, a loro volta, si ottengono utilizzando, come costituenti, i comandi primitivi della shell assieme a quelli già definiti in precedenza.

Fra i comandi predefiniti quello di test riveste un ruolo fondamentale perchè attraverso di esso è possibile implementare la valutazione delle espressioni booleane. Si ricorda, a questo proposito, che la shell non esegue implicitamente alcuna espressione aritmetica o logica ma, soltanto su richiesta del comando test.

Nel seguito verrà trattato diffusamente tale comando e, successivamente, verranno discussi alcuni esempi significativi che riprendono e illustrano alcune delle caratteristiche della shell.

GRAMMATICA DELLA SHELL

A completamento della lista di comandi predefiniti della shell Bash, si riporta nel seguito la descrizione dettagliata delle possibili opzioni utilizzabili con il comando test. Tuttavia, per un maggior approfondimento degli argomenti finora discussi si faccia riferimento al manuale della shell in formato PDF oppure interattivo ipertestuale.

Il comando test args può anche rappresentarsi con una coppia di parantesi quadre [  args ] con l'avvertenza di lasciare uno spazio bianco fra le parentesi quadre e l'espressione da valutare.

A completamento dei meccanismi di riderizione della shell, nel seguito viene discusso l'impiego del simbolo << molto utile per definire "parti di testo dello shell script che devono essere interpretate come input di un certo comando". La sintassi generale è la seguente

COSTRUTTO
BOURNE SHELL
C-LIKE SHELL

Nuovo Input

cmd << mark
.........
....
.........
mark
cmd << mark
.........
....
.........
mark

cosicchè durante l'esecuzione dello script quando si incontra la riga contenente il comando cmd, tutte le righe successive, fino a quella contenente mark vengono passate come input a cmd. Se la costante mark è quotata, allora le righe vengono prese letteralmente, altrimenti si procede alle usuali regole di espansione che la shell implementa sugli argomenti.

Esempi d'Uso della Shell

I tre esempi proposti nel seguito esemplificano la metodologia di implementazione degli shell script e, in particolare, il secondo dove vengono utilizzati tutti i costrutti standard della bash per la manipolazione di variabili e file.

Ordinamento di un file

Il seguente programma riordina le le righe di un file secondo i valori interi crescenti contenuti nel primo campo di ciascuna riga. Vengono utilizzato i programmi di utilità generale wc, cat, head e sort oltre, naturalmente, a sed e expr

#!/bin/bash
#   Usage: fsort file
#
if [ $# -ne 1 ] > /dev/null;
  then
    echo 'insufficient parameter'
    exit 1
fi
NAME=$1
sort +0 $NAME > tmp$$
L=`wc -l $NAME | sed s/$NAME//`
echo $L
K=0
while [ $K -ne $L ]
  do
    K=`expr $K + 1`
    cat `echo -n $K` head -1 tmp$$ >> sort$$
  done
cat tmp$$
/bin/rm -f tmp$$
echo
cat sort$$
/bin/rm -f sort$$
exit 0

Ordinamento di un File

Anche in quest'ultimo esempio l'implementazione si appoggia su comandi di shell già noti (wc, rm, sort, ecc...) ma ora si utilizzano file temporanei di supporto all'elaborazione che, per evitare sovrapposizioni non volute, sono indicizzati coll'identificatore di processo ($$).

Implementazione della Shell

L'interprete della shell, che nel caso di Linux è rappresentato dalla bash, viene attivato ogno qualvolta si apre un X terminal a partire dalla barra degli strumenti della console grafica o si apre una sessione telnet, sia localmente che in modalià remota.

Lo schema di funzionamento dell'interprete è abbastanza semplice. Dopo aver scritto una stringa di prompt sul terminale, l'interprete attende che l'utente introduca una riga di comando. A questo punto si passa alla sua lettura che prevede il riconoscimento del comando con tutti i suoi parametri ed opzioni. Nell'implementazione proposta di seguito, si utilizza il costrutto while per la gestione del ciclo esecutivo, affiancato dalla variabile di controllo di terminazione $HALT. Il comando predefinito read serve a leggere la riga di comando.

La gestione della storia dei comandi è affidata alla variabile $HISTORY che viene inizializzata in base al contenuto del file storico $STORIA e riscritto al termine della sessione. I comandi implementati sono !?, per la lettura dello storico, !del, per la sua cancellazione, !!, per eseguire l'ultimo comando e !n, per l'esecuzione dell'n-esimo comando già eseguito.

#!/bin/bash
# Synopsis: minishell -flag
#    possible flags: c,d,e,s,x

function Banner() {
  echo "Minishell based on Bash shell";
  echo "version 1.0";
  echo;
  if [ -f "$STORIA" ]; then
    HISTORY=$(sed 's/^$/\n/g' $STORIA);
  fi
}

function Execute() {
# Execute(**char $1, char $2, **char $3)
  case "$2" in
    "d") echo "$3" ;;
    "x") $3 ;;
    "e") eval $3 ;;
    "s") ( $3 ) ;;
    "c") command $3 ;;
  esac
  HISTORY=$(echo -e "$1\n$3" | sed "/^$/d");
}
function Help() {
  echo "quit  exit"; 
  echo "!del  delete History log"; 
  echo "!!    execute last command"; 
  echo "!?    list all command"; 
  echo "!n    execute the n-th command"; 
  echo;
}

function get_Args() {
  K=0 ;
  ARGS=""
  for arg in "$1"
    do
      if [ "$K" -eq 0 ]
        then
          CMD=$arg ;
        else
         ARGS="$ARGS $arg" ;
      fi
      K=`expr $K + 1`
    done
}

function Command() {
# Command([TAG] char $1, [M] int $2)
  N=$(echo -e "$HISTORY" | wc -l);
  if [ "$2" -lt "$N"  ]; then
    CMDLINE=$(echo -e "$HISTORY" | tail --lines=+$2 | head -1);
    echo "$PROMPT$CMDLINE"
    Execute "$HISTORY" $1 "$CMDLINE";
    return 0
  else
    return 1
  fi
}

declare -i HALT=0
declare -x ARGS
declare -x CMD

if [ "$#" -lt 1 ]; then
    echo "Possible options are: d,e,x,s,c"
    echo "Try -help for more information"
    exit 1
fi
TAG=`echo $1 | sed 's/-//'`
if [ "$TAG" == "help" ]; then
  Help;
  exit 5
  elif [ "$TAG" != "c" -a "$TAG" != "d" -a "$TAG" != "e" -a \
         "$TAG" != "s" -a "$TAG" != "x" ]; then
    echo "no such an option"
    exit 6
fi

PROMPT="msh> "
STORIA=~/Desktop/mshistory

Banner;
while [ "$HALT" -eq 0 ]; do
  read -p "$PROMPT" ;
  get_Args "$REPLY" ;
  case $CMD in
    quit) HALT=1 ;;

    help) Help;;

    !\?) echo -e "$HISTORY" | cat -b;;

    !del) HISTORY="";;

    !!) CMD=$(echo -e "$HISTORY" | tail -1);
        Execute "$HISTORY" $TAG "$CMD $ARGS";;

    ![0-9]*[a-z]*) HALT=2;;

    ![a-z]*) HALT=3;;

    ![0-9]*) M=$(echo "$CMD" | sed 's/!//');
             Command $TAG $M
             if [ "$?" -ne 0 ]; then
               HALT=4
             fi ;;

    *) Execute "$HISTORY" $TAG "$CMD $ARGS" ;;
  esac
done
echo "$HISTORY" > $STORIA
case $HALT in
  1) exit 0;;
  2) echo "Syntax error"; exit 2 ;;
  3) echo "Syntax error"; exit 3 ;;
  4) echo "Syntax error"; exit 4 ;;
esac

Minishell che ricorda i comandi eseguiti
Si noti, fra l'altro, l'uso di function per rendere l'implementazione più compatta e più leggibile. Si sono impiegate anche le espressioni regolari per definire in modo semplice e compatto i pattern che, soddisfando le condizioni indicate, attivano l'esecuzione dei comandi associati. In questo modo appare immediata la comprensione del funzionamento dell'esecutore che, in ultima analisi, è dato da uno statement case all'interno di un ciclo while. Le azioni riconosciute dalla minishell sono quelle evidenziate come pattern del case. Infine, con la funzione Execute si è voluto mettere in evidenza quali sono le azioni che sono a carico della sottostante shell implementante.

Spedire posta con sendmail

Nel semplice esempio che segue viene utilizzato la ridirezione di input per fornire i parametri necessari al comando sendmail. Il template viene istanziato dai valori correnti associati ai paramentri posizionali durante la chiamata del comando msend la cui unica funzione è quella di preparare i parametri per sendmail.

#/bin/bash
if [ "$#" -eq 0 ]
  then
    echo "Usage: msend recipient subject integer"
    exit 1
fi
sendmail $1 << EOT
subject: $2
questo e' il messaggio di prova n. $3
EOT
exit 0

Template da spedire via e-mail
In questo caso, poichè il mark EOT non è quotato i parametri posizionali vengono espansi secondo i valori acquisiti al momemto della chiamata del comando msend dalla shell.