33.2. Shell wrapper

Un "wrapper" è uno script di shell che incorpora una utility o un comando di sistema. Questo evita di dover digitare una serie di parametri che andrebbero passati manualmente a quel comando. [1] "Avvolgere" uno script attorno ad una complessa riga di comando ne semplifica l'invocazione. Questo è particolarmente utile con sed e awk.

Uno script sed o awk, di norma, dovrebbe essere invocato da riga di comando con sed -e 'comandi' o awk 'comandi'. Inserire un tale script in uno script Bash permette di richiamarlo in modo più semplice, rendendolo anche "riutilizzabile". In questo modo è anche possibile combinare le funzionalità di sed e awk, per esempio collegando con una pipe l'output di una serie di comandi sed a awk. Se salvato come file eseguibile può essere ripetutamente invocato, nella sua forma originale o modificata, senza l'inconveniente di doverlo ridigitare completamente da riga di comando.

Esempio 33-1. Shell wrapper

#!/bin/bash

# Questo è un semplice script che rimuove le righe vuote da un file.
# Nessuna verifica d'argomento.
#
# Sarebbe meglio aggiungere qualcosa come:

# E_NOARG=65
# if [ -z "$1" ]
# then
#  echo "Utilizzo: `basename $0` nome-file"
#  exit $E_NOARG
# fi


# È uguale a
#    sed -e '/^$/d' nomefile
# invocato da riga di comando.

sed -e /^$/d "$1"
#  '-e' significa che segue un comando di "editing" (in questo caso opzionale).
#  '^' indica l'inizio della riga, '$' la fine.
#  Verifica le righe che non contengono nulla tra il loro inizio e la fine,
#+ vale a dire, le righe vuote.
#  'd' è il comando di cancellazione.

#  L'uso del quoting per l'argomento consente di
#+ passare nomi di file contenenti spazi e caratteri speciali.

#  Va notato che lo script, in realtà, non modifica il file di riferimento.
#  Se avete questa necessità, effettuate la redirezione dell'output.

exit 0

Esempio 33-2. Uno shell wrapper leggermente più complesso

#!/bin/bash

#  "subst", uno script per sostituire un nome
#+ con un altro all'interno di un file,
#+ es. "subst Smith Jones letter.txt".

ARG=3          # Lo script richiede tre argomenti.
E_ERR_ARG=65   # Numero errato di argomenti passati allo script.

if [ $# -ne "$ARG" ]
# Verifica il numero degli argomenti (è sempre una buona idea).
then
  echo "Utilizzo: `basename $0` vecchio-nome nuovo-nome nomefile"
  exit $E_ERR_ARG
fi

vecchio_nome=$1
nuovo_nome=$2

if [ -f "$3" ]
then
  nome_file=$3
else
  echo "Il file \"$3\" non esiste."
  exit $E_ERR_ARG
fi

#  Ecco dove viene svolto il lavoro principale.

# -----------------------------------------------
sed -e "s/$vecchio_nome/$nuovo_nome/g" $nome_file
# -----------------------------------------------

#  's' è, naturalmente, il comando sed di sostituzione,
#+ e /modello/ invoca la ricerca di corrispondenza.
#  L'opzione "g", o globale, provoca la sostituzione di *tutte*
#+ le occorrenze di $vecchio_nome in ogni riga, non solamente nella prima.
#  Leggete i testi riguardanti 'sed' per una spiegazione più approfondita.

exit 0    # Lo script invocato con successo restituisce 0.

Esempio 33-3. Uno shell wrapper generico che effettua una registrazione in un file di log

#!/bin/bash
#  Uno shell wrapper generico che effettua una/delle operazione/i
#+ registrandola/e in un file di log.

# Si devono impostare le variabili seguenti.
OPERAZIONE=
#         Può essere una serie complessa di comandi,
#+        per esempio uno script awk o una pipe . . .
LOGFILE=
#         File di log.


OPZIONI="$@"
#         Argomenti da riga di comando, se ce ne fossero, per operazione.

# Registrazione.
echo "`date` + `whoami` + $OPERAZIONE "$@"" >> $LOGFILE
# Ora l'esecuzione.
exec $OPERAZIONE "$@"

# È necessario effettuare la registrazione prima dell'esecuzione.
# Perché?

Esempio 33-4. Uno shell wrapper per uno script awk

#!/bin/bash
# pr-ascii.sh: Visualizza una tabella di caratteri ASCII.

INIZIO=33   # Intervallo dei caratteri ASCII stampabili (decimali).
FINE=125

echo " Decimale  Esa     Carattere"   # Intestazione.
echo " --------  ---     ---------"

for ((i=INIZIO; i<=FINE; i++))
do
  echo $i | awk '{printf("  %3d       %2x         %c\n", $1, $1, $1)}'
# In questo contesto, il builtin Bash printf non funziona:
#     printf "%c" "$i"
done

exit 0


#  Decimale  Esa     Carattere
#  --------  ---     ---------
#    33       21         !
#    34       22         "
#    35       23         #
#    36       24         $
#
#    . . .
#
#   122       7a         z
#   123       7b         {
#   124       7c         |
#   125       7d         }


#  Redirigete l'output dello script in un file
#+ o collegatelo con una pipe a "more":  sh pr-asc.sh | more

Esempio 33-5. Uno shell wrapper per un altro script awk

#!/bin/bash

# Aggiunge la colonna specificata (di numeri) nel file indicato.

ARG=2
E_ERR_ARG=65

if [ $# -ne "$ARG" ] #  Verifica il corretto nr. di argomenti da riga 
                     #+ di comando.
then
   echo "Utilizzo: `basename $0` nomefile numero-colonna"
   exit $E_ERR_ARG
fi

nomefile=$1
numero_colonna=$2

#  Il passaggio di variabili di shell allo script awk incorporato 
#+ è un po' complicato.
#  Un metodo consiste nell'applicare il quoting forte alla variabile dello 
#+ script Bash all'interno dello script awk.
#     $'$VAR_SCRIPT_BASH'
#      ^                ^
#  È ciò che è stato fatto nello script awk incorporato che segue.
#  Vedete la documentazione awk per maggiori dettagli.

# Uno script awk che occupa più righe viene invocato con:  awk ' ..... '


# Inizio dello script awk.
# -----------------------------
awk '

{ totale += $'"${numero_colonna}"'
}
END {
      print totale
}

' "$nomefile"
# -----------------------------
# Fine dello script awk.


#   Potrebbe non essere sicuro passare variabili di shell a uno script awk
#+  incorporato, così Stephane Chazelas propone la seguente alternativa:
#   ---------------------------------------
#   awk -v numero_colonna="$numero_colonna" '
#   { totale += $numero_colonna
#   }
#   END {
#       print totale
#   }' "$nomefile"
#   ---------------------------------------


exit 0

Per quegli script che necessitano di un unico strumento tuttofare, un coltellino svizzero informatico, esiste Perl. Perl combina le capacità di sed e awk, e, per di più, un'ampia parte di quelle del C. È modulare e supporta qualsiasi cosa, dalla programmazione orientata agli oggetti fino alla preparazione del caffè. Brevi script in Perl si prestano bene ad essere inseriti in script di shell e si può anche dichiarare, con qualche ragione, che Perl possa sostituire completamente lo scripting di shell stesso (sebbene l'autore di questo documento rimanga scettico).

Esempio 33-6. Perl inserito in uno script Bash

#!/bin/bash

# I comandi shell possono precedere lo script Perl.
echo "Questa riga precede lo script Perl inserito in \"$0\"."
echo "==============================================================="

perl -e 'print "Questo è lo script Perl che è stato inserito.\n";'
# Come sed, anche Perl usa l'opzione "-e".

echo "==============================================================="
echo "Comunque, lo script può contenere anche comandi di shell e di sistema."

exit 0

È anche possibile combinare, in un unico file, uno script Bash e uno script Perl. Dipenderà dal modo in cui lo script verrà invocato quale delle due parti sarà eseguita.

Esempio 33-7. Script Bash e Perl combinati

#!/bin/bash
# bashandperl.sh

echo "Saluti dalla parte Bash dello script."
# Qui possono seguire altri comandi Bash.

exit 0
# Fine della parte Bash dello script.

# =======================================================

#!/usr/bin/perl
# Questa parte dello script deve essere invocata con l'opzione -x.

print "Saluti dalla parte Perl dello script.\n";
# Qui possono seguire altri comandi Perl.

# Fine della parte Perl dello script.

bash$ bash bashandperl.sh
Saluti dalla parte Bash dello script.


bash$ perl -x bashandperl.sh
Saluti dalla parte Perl dello script.
	      

Note

[1]

Un certo numero di utility Linux sono, in effetti, dei shell wrapper. Alcuni esempi sono /usr/bin/pdf2ps, /usr/bin/batch e /usr/X11R6/bin/xmkmf.