LEZIONE del 12 Novembre

La shell di Unix, nelle sue diverse varianti Bourne, C-shell, Korn-shell, bash, ecc., mette a disposizione dell'utente una ricca varietà di costrutti e di comandi predefiniti che permettono l'accesso alle molte risorse che tale sistema operativo offre sia in campo applicativo che di gestione del sistema stesso.

In particolare, la shell ufficiale di Linux, ossia la bash, si presenta particolarmente ricca di estensioni rispetto alla Bourne shell sh da richiedere un corso completo sull'argomento per sviscerarne tutte le potenzialità e le inevitabili difficoltà.

Come ogni linguaggio interpretato, anche la shell deve soggiacere a determinati vincoli se si vuole rendere l'interprete contemporaneamente snello ed efficiente. In particolare, la gestione dei parametri e i meccanismi di dereferenziamento vanno resi efficaci e di facile comprensione.

GRAMMATICA DELLA SHELL

Il trattamento delle variabili di shell è particolarmente delicato perchè richiede una certa attenzione da parte dell'utente per il diverso meccanismo di funzionamento rispetto a quello utilizzato nel trattamento delle variabili dei linguaggi di programmazione evoluti, ma convenzionali, come C, PASCAL, MODULA, JAVA, ecc...

Infatti, nel caso della shell, sebbene si possa parlare di tipi di dati ben definiti come gli interi, le stringhe o i descrittori di file, in realtà siamo di fronte a variabili non strutturate se confrontate con gli usuali linguaggi di programmazione.

CONTROLLO DELL'ESECUZIONE DI UN DEMONE NELLA SHELL

Come è stato più volte sottolineato la shell è il modo standard con cui l'utente può interagire col sistema ottenendo le prestazioni richieste con un semplice meccanismo di interpretazione dei comandi non appena questi sono notificati al sistema con il tasto di invio.

L'interprete della shell, che nel caso di Linux è rappresentato dalla bash, viene attivato ogni 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.

Nel caso si voglia lanciare dalla shell un demone, ossia un servizio che rimane attivo in attesa di richieste dai processi client su una porta ben specificata, si deve procedere a fornire alla shell alcune azioni per il controllo dell'esecuzione dei suddetti servizi. A tal scopo viene definito il comando di shell daemon attraverso il quale è possibile lanciare, fermare e interrogare lo stato di esecuzione del servizio. Si tenga presente che, fra l'altro, lo scopo di daemon è anche quello di mantenere il servizio in esecuzione in background assegnandoli le necessarie risorse.

Lo schema di funzionamento del gestore di servizi daemon che appare nel seguito delinea le caratteristiche essenziali di tale comando, il cui funzionamento si basa su alcune variabili d'ambiente che possono essere lette e/o modificate. Nel caso in esame si suppone di dover gestire, per ogni servizio, la porta d'accesso e la possibilità di avere per esso uno specifico file di configurazione.

L'implementazione proposta si basa sul fatto che tutte le informazioni relative allo stato dei processi sono contenute in una struttura dati accessibile mediante il comando di shell ps. Dunque, non è necessario mantenere attivo il gestore daemon ma è sufficiente far si che che ad ogni esecuzione, le informazioni rilevanti sullo stato dei processi in esecuzione da esso controllati, siano accessibili e/o modificabili.

#!/bin/bash
#
# daemon:    Start, Stop, Status, Restart a given Daemon
#   Synopsis: daemon <daemon> [start|stop|status|restart]
#
# config: ./${prog}.conf
#
RES_COL=60
MOVE_TO_COL="echo -en \\033[${RES_COL}G"
SETCOLOR_SUCCESS="echo -en \\033[1;32m"
SETCOLOR_FAILURE="echo -en \\033[1;31m"
SETCOLOR_WARNING="echo -en \\033[1;33m"
SETCOLOR_NORMAL="echo -en \\033[0;39m"

# Log that something succeeded
function success() {
  $MOVE_TO_COL
  echo -n "["
  $SETCOLOR_SUCCESS
  echo -n $"  OK  "
  $SETCOLOR_NORMAL
  echo -n "]"
  echo -ne "\r"
  return 0
}

# Log that something failed
function failure() {
  $MOVE_TO_COL
  echo -n "["
  $SETCOLOR_FAILURE
  echo -n $"FAILED"
  $SETCOLOR_NORMAL
  echo -n "]"
  echo -ne "\r"
  return 0
}

function start() {
  echo -n "Starting $1 daemon: ";
  $1 $PORT > ${1}.log &
  RETVAL=$?
  [ $RETVAL -eq 0 ] && success $"$1" || failure $"$1"
  echo
  return $RETVAL
}

function stop() {
  echo -n "Shutting down $1 daemon: "
  PSB=(`ps ax | grep -e " \<$1\>" | grep -v "grep"`)
  if [ "${PSB[0]}" != "" ]; then  > /dev/null
    kill -9 ${PSB[0]} > /dev/null
    RETVAL=$? > /dev/null
    [ $RETVAL -eq 0 ] && success $"$1" || failure $"$1"
  else
    RETVAL=1
    failure $"$1"
  fi
  return $RETVAL
}

# Output PIDs of matching processes, found using pidof
function Findpidof() {
  pidof -c -o $$ -o $PPID -o %PPID -x "$1" || \
  pidof -c -o $$ -o $PPID -o %PPID -x "${1##*/}"
}

function status() {
  local base pid
  base=${1##*/}

  pid="$(Findpidof "$1")"
  if [ -n "$pid" ]; then
    echo $"${base} (pid $pid) is running..."
    return 0
  fi
  echo $"${base} is stopped"
  return 3
}

export -f start
export -f stop
export -f status

function daemonctrl() {
  CONFIG="$DIRCONF/${prog}.conf"
  if [ -f $CONFIG ]; then
          eval `$CONFIG`
  fi

  case "$ACTION" in
    start)
      start $prog; RETVAL=$? ;;

    stop)
      stop $prog; RETVAL=$? ;;

    status)
	    status $prog; RETVAL=$? ;;
    restart)
	    stop $prog; start $prog; RETVAL=$? ;;
    *)
      USAGE1="*** Usage: daemon <daemon> ";
      USAGE2="start|stop|status|restart";
	    echo "${USAGE1}${USAGE2}" ;
	    exit 1 ;;
  esac
  return $RETVAL
}

if [ "$#" -lt 2 ]; then
  echo "Insufficient parameters";
  USAGE1="*** Usage: daemon <daemon> ";
  USAGE2="start|stop|status|restart";
	echo "${USAGE1}${USAGE2}" ;
  exit 1
fi

PROGNAME=$1
ACTION=$2
if [ `echo $PROGNAME | grep -e "/"` ]; then > /dev/null
  PROG=(`echo $PROGNAME | sed 's!/! !g'`);
  let "nProg = ${#PROG[@]} - 1";
  prog="${PROG[$nProg]}";
else
  prog="${PROGNAME}";
fi

export RETVAL=0
export prog

# Default configuration
export HOST="localhost"
export DIRCONF="."
export PORT=6000

export -f daemonctrl
daemonctrl
exit $?

Attivazione, controllo e arresto di un demone

Nell'implementazione proposta l'esecuzione del comando è ottenuta in modalità command. Sono tuttavia possibili altri modi: esecuzione diretta della stringa $REPLY contenente il comando in questione, modalià eval, exec oppure in subshell.

INTERPRETAZIONE DELLE VARIABILI

L'interpretazione delle variabili da parte della shell ne costituisce uno dei suoi, aspetti caratteristici, noto col termine di espansione. Tale azione è effettuata sulla riga di comando non appena è stata divisa in parole. Le espansione effettuati sono di sette tipi e riguardano le parentesi graffe, la tilde, i parametri e le variabili, le espressioni aritmetiche e i percorsi nel file system. Ad esse si aggiungono la sostituzione di comando e la suddivisione in parole".

Il carattere $ introduce l'espansione di parametro, la sostituzione di comando, o l'espansione aritmetica. Il nome o simbolo del parametro che deve essere espanso può essere racchiuso tra parentesi graffe, che sono opzionali ma servono a proteggere la variabile che deve essere espansa, dai caratteri immediatamente seguenti, che potrebbero essere interpretati come parte del nome.

In ciascuno dei casi seguenti, parola è soggetta all'espansione di parametro e della tilde, alla sostituzione di comando e all'espansione aritmetica. La shell controlla se un parametro non è stato assegnato o è nullo; l'omissione dei due punti provoca il solo controllo di parametro non assegnato.

SOSTITUZIONE DI COMANDO

La sostituzione comando viene utilizzata ogniqualvolta si richiede che l'output di un comando rimpiazzi il nome del comando. Vi sono due forme:

La shell effettua l'espansione eseguendo il comando e rimpiazzando la sostituzione di comando con lo standard output del comando, con ogni newline finale cancellato.

Quando viene utilizzata la forma di sostituzione in vecchio stile, con il backquote, il backslash conserva il suo significato letterale tranne quando seguito da $, ` o \. Quando si usa la forma $(comando), tutti i caratteri tra le parentesi formano il comando; nessuno è trattato in modo speciale.

La sostituzione di comando può essere nidificata. Per nidificare quando si usa la vecchia forma, bisogna far precedere le backquote più interne con un carattere backslash di escape. Se la sostituzione compare tra virgolette, la suddivisione in parole e l'espansione di percorso non sono effettuate sui risultati.

ESPANSIONE ARITMETICA

L'espansione artimetica permette la valutazione di una espressione aritmetica e la sostituzione del risultato. Vi sono due formati per l'espansione aritmetica:

L'espressione è trattata come se fosse tra virgolette, ma le virgolette dentro le parentesi graffe o tonde non sono trattate in modo speciale. Tutti i token nella espressione subiscono l'espansione di parametro, la sostituzione comando e la rimozione dei caratteri di quotatura. Le sostituzioni aritmetiche possono essere nidificate.
$ LIMIT=15
> for ((I=0; I <= LIMIT; I++)); do
>   echo -n "$I "
> done; echo; echo

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Il calcolo viene effettuato in accordo con le regole del calcolo aritmetico definite nella shell. Se l'espressione non è valida, viene stampato un messaggio che indica il fallimento e non avviene alcuna sostituzione.

ESPANSIONE DI PERCORSO

Effettuata la suddivisione in parole, a meno che non sia stata impostata l'opzione -f, la shell scandisce ogni parola alla ricerca dei caratteri *, ? e [. Se compare uno di questi caratteri allora la parola, considerata come un pattern, viene sostituita con una lista di percorsi combacianti con il pattern, ordinata alfabeticamente.

Se non viene trovato alcun percorso combaciante, e la variabile di shell allow_null_glob_expansion non è impostata, la parola rimane inalterata. Se la variabile è impostata, ma non è stata trovata alcuna coincidenza, la parola viene rimossa. Quando un pattern è usato per la generazione di percorsi, il carattere . all'inizio di un nome o immediatamente seguente uno slash deve combaciare esplicitamente, a meno che la variabile di shell glob_dot_filenames non sia stata impostata. Il carattere slash deve sempre combaciare esplicitamente. Negli altri casi, il carattere . non è trattato in modo speciale.

I caratteri speciali dei pattern hanno i seguenti significati:

Se il primo carattere che segue [ è ! oppure ^, allora qualunque carattere non compreso in parantesi è combaciante. Un - o ] può essere confrontato includendolo come primo o ultimo carattere dell'insieme.

IMPIEGO DEGLI ARRAY

Si è già visto quali sono i meccanismi attraverso i quali la shell bash permette all'utente di utilizzare le variabili nella stesura di uno script: qualunque identificatore preceduto dal simbolo speciale $ assume il significato di "portatore di valore". La loro introduzione è implicita al momento della prima assegnazione. Tuttavia, la shell permette anche una loro dichiarazione esplicita se si utlizza il comando declare che consente di assegnare un tipo alla variabile.

In generale la shell tratta i valori come stringhe, a meno che i caratteri siano tutti cifre per cui la stringa viene automaticamente interpretata come valore purchè non sia quotata. Tuttavia si può definire una variabile esplicitamente di tipo intero attraverso la dichiarazione

Analogamente, la dichiarazione

Si noti, comunque, che la dichiarazione di DATE come variabile di tipo array non è strettamente necessaria in quanto il tipo viene associato implicitamente in base al valore assegnato che, in questo caso, sono una lista di 3 interi.