Capitolo 14. Comandi interni e builtin

Un builtin è un comando appartenente alla serie degli strumenti Bash, letteralmente incorporato. Questo è stato fatto sia per motivi di efficienza -- i builtin eseguono più rapidamente il loro compito di quanto non facciano i comandi esterni, che di solito devono generare un processo separato (forking) -- sia perché particolari builtin necessitano di un accesso diretto alle parti interne della shell.

Un builtin può avere un nome identico a quello di un comando di sistema. In questo caso Bash lo reimplementa internamente. Per esempio, il comando Bash echo non è uguale a /bin/echo, sebbene la loro azione sia quasi identica.

#!/bin/bash

echo "Questa riga usa il builtin \"echo\"."
/bin/echo "Questa riga usa il comando di sistema  /bin/echo."

Una parola chiave è un simbolo, un operatore o una parola riservata. Le parole chiave hanno un significato particolare per la shell e, infatti, rappresentano le componenti strutturali della sua sintassi . Ad esempio "for", "while", "do" e "!" sono parole chiave. Come un builtin, una parola chiave è una componente interna di Bash, ma a differenza di un builtin, non è di per se stessa un comando, ma parte di una struttura di comandi più ampia. [1]

I/O

echo

visualizza (allo stdout) un'espressione o una variabile (vedi Esempio 4-1).

echo Ciao 
echo $a

echo richiede l'opzione -e per visualizzare le sequenze di escape. Vedi Esempio 5-2.

Normalmente, ogni comando echo visualizza una nuova riga. L'opzione -n annulla questo comportamento.

Nota

echo può essere utilizzato per fornire una sequenza di comandi in una pipe.

if echo "$VAR" | grep -q txt   # if [[ $VAR = *txt* ]]
then
  echo "$VAR contiene la sottostringa \"txt\""
fi

Nota

Si può utilizzare echo, in combinazione con la sostituzione di comando, per impostare una variabile.

a=`echo "CIAO" | tr A-Z a-z`

Vedi anche Esempio 15-19, Esempio 15-3, Esempio 15-42 ed Esempio 15-43.

Si faccia attenzione che echo `comando` cancella tutti i ritorni a capo generati dall'output di comando.

La variabile $IFS (internal field separator), di norma, comprende \n (ritorno a capo) tra i suoi caratteri di spaziatura. Bash, quindi, scinde l'output di comando in corrispondenza dei ritorni a capo. Le parti vengono passate come argomenti a echo. Di conseguenza echo visualizza questi argomenti separati da spazi.

bash$ ls -l /usr/share/apps/kjezz/sounds
-rw-r--r--    1 root     root         1407 Nov  7  2000 reflect.au
-rw-r--r--    1 root     root          362 Nov  7  2000 seconds.au



bash$ echo `ls -l /usr/share/apps/kjezz/sounds`
total 40 -rw-r--r-- 1 root root 716 Nov 7 2000 reflect.au -rw-r--r-- 1 root root 362 Nov 7 2000 seconds.au
	      

Quindi, in che modo si può inserire un "a capo" in una stringa di caratteri da visualizzare con echo?

# Incorporare un a capo?
echo "Perché questa stringa non viene \n suddivisa su due righe?"
# Non viene divisa.

# Proviamo qualcos'altro.

echo

echo $"Riga di testo contenente
un a capo."
# Viene visualizzata su due righe distinte (a capo incorporato).
# Ma, il prefisso di variabile "$" è proprio necessario?

echo

echo "Questa stringa è divisa
su due righe."
# No, il "$" non è necessario.

echo
echo "---------------"
echo

echo -n $"Un'altra riga di testo contenente
un a capo."
# Viene visualizzata su due righe (a capo incorporato).
# In questo caso neanche l'opzione -n riesce a sopprimere l'a capo.

echo
echo
echo "---------------"
echo
echo

# Tuttavia, quello che segue non funziona come potremmo aspettarci.
# Perché no? Suggerimento: assegnamento a una variabile.
stringa1=$"Ancora un'altra riga di testo contenente
un a capo (forse)."

echo $stringa1
# Ancora un'altra riga di testo contenente un a_capo (forse).
#                                         ^
# L'a_capo è diventato uno spazio.

# Grazie a Steve Parker per la precisazione.

Nota

Questo comando è un builtin di shell e non è uguale a /bin/echo, sebbene la sua azione sia simile.

bash$ type -a echo
echo is a shell builtin  
echo is /bin/echo
	      

printf

Il comando printf, visualizzazione formattata, rappresenta un miglioramento di echo. È una variante meno potente della funzione di libreria printf() del linguaggio C. Anche la sua sintassi è un po' differente.

printf stringa-di-formato... parametro...

È la versione builtin Bash del comando /bin/printf o /usr/bin/printf. Per una descrizione dettagliata, si veda la pagina di manuale di printf (comando di sistema).

Attenzione

Le versioni più vecchie di Bash potrebbero non supportare printf.

Esempio 14-2. printf in azione

#!/bin/bash
# printf demo

PI=3,14159265358979                           # Vedi nota a fine listato
CostanteDecimale=31373
Messaggio1="Saluti,"
Messaggio2="un abitante della Terra."

echo

printf "Pi con 2 cifre decimali = %1.2f" $PI
echo
printf "Pi con 9 cifre decimali = %1.9f" $PI  #  Esegue anche il corretto
                                              #+ arrotondamento.

printf "\n"                                   #  Esegue un ritorno a capo,
                                              #  equivale a 'echo'.

printf "Costante = \t%d\n" $CostanteDecimale  #  Inserisce un carattere 
                                              #+ di tabulazione (\t)

printf "%s %s \n" $Messaggio1 $Messaggio2

echo

# ==================================================#
# Simulazione della funzione sprintf del C.
# Impostare una variabile con una stringa di formato.

echo

Pi12=$(printf "%1.12f" $PI)
echo "Pi con 12 cifre decimali = $Pi12"

Msg=`printf "%s %s \n" $Messaggio1 $Messaggio2`
echo $Msg; echo $Msg

#  Ora possiamo disporre della funzione 'sprintf' come modulo
#+ caricabile per Bash. Questo, però, non è portabile.

exit 0

#  N.d.T. Nella versione originale veniva usato il punto come separatore
#+ decimale. Con le impostazioni locali italiane il punto avrebbe
#+ impedito il corretto funzionamento di printf.

Un'utile applicazione di printf è quella di impaginare i messaggi d'errore

E_ERR_DIR=65

var=directory_inesistente

errore()
{
  printf "$@" >&2
  # Organizza i parametri posizionali passati e li invia allo stderr.
  echo
  exit $E_ERR_DIR
}

cd $var || errore $"Non riesco a cambiare in %s." "$var"

# Grazie, S.C.

read

"Legge" il valore di una variabile dallo stdin, vale a dire, preleva in modo interattivo l'input dalla tastiera. L'opzione -a permette a read di assegnare le variabili di un array (vedi Esempio 26-6).

Esempio 14-3. Assegnamento di variabile utilizzando read

#!/bin/bash
# "Leggere" variabili.

echo -n "Immetti il valore della variabile 'var1': "
# L'opzione -n di echo sopprime il ritorno a capo.

read var1
#  Notate che non vi è nessun '$' davanti a var1, perché la variabile
#+ è in fase di impostazione.

echo "var1 = $var1"


echo

# Un singolo enunciato 'read' può impostare più variabili.
echo -n "Immetti i valori delle variabili 'var2' e 'var3' (separati da \
uno spazio o da tab): "
read var2 var3
echo "var2 = $var2      var3 = $var3"
#  Se si immette un solo valore, le rimanenti variabili restano non
#+ impostate (nulle).

exit 0

Se a read non è associata una variabile, l'input viene assegnato alla variabile dedicata $REPLY.

Esempio 14-4. Cosa succede quando a read non è associata una variabile

#!/bin/bash
# read-novar.sh

echo

# -------------------------- #
echo -n "Immetti un valore: "
read var
echo "\"var\" = "$var""
# Tutto come ci si aspetta.
# -------------------------- #

echo

# ---------------------------------------------------------------- #
echo -n "Immetti un altro valore: "
read           #  Non viene fornita alcuna variabile a 'read',
               #+ quindi... l'input di 'read' viene assegnato alla
               #+ variabile predefinita $REPLY.
var="$REPLY"
echo "\"var\" = "$var""
# Stesso risultato del primo blocco di codice.
# ---------------------------------------------------------------- #

echo

exit 0

#  Questo esempio è simile allo script "reply.sh".
#  Mostra, però, che $REPLY è ancora disponibile
#+ anche dopo un 'read' con una variabile specificata.

Normalmente, immettendo una \ nell'input di read si disabilita il ritorno a capo. L'opzione -r consente di interpretare la \ letteralmente.

Esempio 14-5. Input su più righe per read

#!/bin/bash

echo

echo "Immettete una stringa che termina con \\, quindi premete <INVIO>."
echo "Dopo di che, immettete una seconda stringa e premete ancora <INVIO>."
read var1     # La "\" sopprime il ritorno a capo durante la lettura di $var1.
              #     prima riga \
              #     seconda riga

echo "var1 = $var1"
#     var1 = prima riga seconda riga

#  Per ciascuna riga che termina con "\", si ottiene un prompt alla riga 
#+ successiva per continuare ad inserire caratteri in var1.

echo; echo

echo "Immettete un'altra stringa che termina con \\ , quindi premete <INVIO>."
read -r var2  # L'opzione -r fa sì che "\" venga interpretata letteralmente.
              #     prima riga \

echo "var2 = $var2"
#     var2 = prima riga \

# L'introduzione dei dati termina con il primo <INVIO>.

echo 

exit 0

Il comando read possiede alcune interessanti opzioni che consentono di visualizzare un prompt e persino di leggere i tasti premuti senza il bisogno di premere INVIO.

# Rilevare la pressione di un tasto senza dover premere INVIO.

read -s -n1 -p "Premi un tasto " tasto
echo; echo "Hai premuto il tasto "\"$tasto\""."

#  L'opzione -s serve a non visualizzare l'input.
#  L'opzione -n N indica che devono essere accettati solo N caratteri di input.
#  L'opzione -p permette di visualizzare il messaggio del prompt immediatamente
#+ successivo, prima di leggere l'input.

#  Usare queste opzioni è un po' complicato, perché
#+ devono essere poste nell'ordine esatto.

L'opzione -n di read consente anche il rilevamento dei tasti freccia ed alcuni altri tasti inusuali.

Esempio 14-6. Rilevare i tasti freccia

#!/bin/bash
# arrow-detect.sh: Rileva i tasti freccia, e qualcos'altro.
# Grazie a Sandro Magi per avermelo mostrato.

# --------------------------------------------------------
# Codice dei caratteri generati dalla pressione dei tasti.
frecciasu='\[A'
frecciagiù='\[B'
frecciadestra='\[C'
frecciasinistra='\[D'
ins='\[2'
canc='\[3'
# --------------------------------------------------------

SUCCESSO=0
ALTRO=65

echo -n "Premi un tasto...  "
#  Potrebbe essere necessario premere anche INVIO se viene premuto un
#+ tasto non tra quelli elencati.
read -n3 tasto                        # Legge 3 caratteri.

echo -n "$tasto" | grep "$frecciasu"  #  Verifica il codice del
                                      #+ tasto premuto.
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto Freccia-su."
  exit $SUCCESSO
fi

echo -n "$tasto" | grep "$frecciagiù"
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto Freccia-giù."
  exit $SUCCESSO
fi

echo -n "$tasto" | grep "$frecciadestra"
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto Freccia-destra."
  exit $SUCCESSO
fi

echo -n "$tasto" | grep "$frecciasinistra"
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto Freccia-sinistra."
  exit $SUCCESSO
fi

echo -n "$tasto" | grep "$ins"
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto \"Ins\"."
  exit $SUCCESSO
fi

echo -n "$tasto" | grep "$canc"
if [ "$?" -eq $SUCCESSO ]
then
  echo "È stato premuto il tasto \"Canc\"."
  exit $SUCCESSO
fi


echo " È stato premuto un altro tasto."

exit $ALTRO

#  Esercizi:
#  --------
#  1) Semplificate lo script trasformando le verifiche multiple "if" in un 
#     costrutto 'case'.
#  2) Aggiungete il rilevamento dei tasti "Home", "Fine", "PgUp" e "PgDn".

#  N.d.T. Attenzione! I codici dei tasti indicati all'inizio potrebbero non 
#+ corrispondere a quelli della vostra tastiera.
#  Verificateli e quindi, modificate l'esercizio in modo che funzioni
#+ correttamente.

Nota

L'opzione -n di read evita il rilevamento del tasto INVIO (nuova riga).

L'opzione -t di read consente un input temporizzato (vedi Esempio 9-4).

Il comando read può anche "leggere" il valore da assegnare alla variabile da un file rediretto allo stdin. Se il file contiene più di una riga, solo la prima viene assegnata alla variabile. Se read ha più di un parametro, allora ad ognuna di queste variabili vengono assegnate le stringhe successive delimitate da spazi. Attenzione!

Esempio 14-7. Utilizzare read con la redirezione di file

#!/bin/bash

read var1 <file-dati
echo "var1 = $var1"
# var1 viene impostata con l'intera prima riga del file di input "file-dati"

read var2 var3 <file-dati
echo "var2 = $var2   var3 = $var3"
# Notate qui il comportamento poco intuitivo di "read".
# 1) Ritorna all'inizio del file di input.
# 2) Ciascuna variabile viene impostata alla stringa corrispondente,
#    separata da spazi, piuttosto che all'intera riga di testo.
# 3) La variabile finale viene impostata alla parte rimanente della riga.
# 4) Se ci sono più variabili da impostare di quante siano le
#    stringhe separate da spazi nella prima riga del file, allora le
#    variabili in eccesso restano vuote.

echo "------------------------------------------------"

# Come risolvere il problema precedente con un ciclo:
while read riga
do
  echo "$riga"
done <file-dati
# Grazie a Heiner Steven per la puntualizzazione.

echo "------------------------------------------------"

#  Uso della variabile $IFS (Internal Field Separator) per suddividere
#+ una riga di input per "read",
#+ se non si vuole che il delimitatore preimpostato sia la spaziatura.

echo "Elenco di tutti gli utenti:"
OIFS=$IFS; IFS=:       # /etc/passwd usa ":" come separatore di campo.
while read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done <etc/passwd       # Redirezione I/O.
IFS=$OIFS              # Ripristina il valore originario di $IFS.
# Anche questo frammento di codice è di Heiner Steven.



#  Impostando la variabile $IFS all'interno dello stesso ciclo,
#+ viene eliminata la necessità di salvare il valore originario
#+ di $IFS in una variabile temporanea.
#  Grazie, Dim Segebart per la precisazione.
echo "------------------------------------------------"
echo "Elenco di tutti gli utenti:"

while IFS=: read name passwd uid gid fullname ignore
do
  echo "$name ($fullname)"
done <etc/passwd       # Redirezione I/O.

echo
echo "\$IFS è ancora $IFS"

exit 0

Nota

Il tentativo di impostare delle variabili collegando con una pipe l'output del comando echo a read, fallisce.

Tuttavia, collegare con una pipe l'output di cat sembra funzionare.

cat file1 file2 |
while read riga
do
echo $riga
done

Comunque, come mostra Bjön Eriksson:

Esempio 14-8. Problemi leggendo da una pipe

#!/bin/sh
# readpipe.sh
# Esempio fornito da Bjon Eriksson.

ultimo="(null)"
cat $0 |
while read riga
do
    echo "{$riga}"
    ultimo=$riga
done
printf "\nFatto, ultimo:$ultimo\n"

exit 0  # Fine del codice.
        # Segue l'output (parziale) dello script.
        # 'echo' fornisce le parentesi graffe aggiuntive.

########################################################

./readpipe.sh 

{#!/bin/sh}
{ultimo="(null)"}
{cat $0 |}
{while read riga}
{do}
{echo "{$riga}"}
{ultimo=$riga}
{done}
{printf "nFatto, ultimo:$ultimon"}


Fatto, ultimo:(null)

La variabile (ultimo) è stata impostata all'interno di una subshell,
al di fuori di essa, quindi, rimane non impostata.

Lo script gendiff, che di solito si trova in /usr/bin in molte distribuzioni Linux, usa una pipe per collegare l'output di find ad un costrutto while read.

find $1 \( -name "*$2" -o -name ".*$2" \) -print |
while read f; do
. . .

Filesystem

cd

Il familiare comando di cambio di directory cd viene usato negli script in cui, per eseguire un certo comando, è necessario trovarsi in una directory specifica.

(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
[dal già citato esempio di Alan Cox]

L'opzione -P (physical) di cd permette di ignorare i link simbolici.

cd - cambia a $OLDPWD, la directory di lavoro precedente.

Attenzione

Il comando cd non funziona come ci si potrebbe aspettare quando è seguito da una doppia barra.

bash$ cd //
bash$ pwd
//
	      
L'output, naturalmente, dovrebbe essere /. Questo rappresenta un problema sia da riga di comando che in uno script.

pwd

Print Working Directory. Fornisce la directory corrente dell'utente (o dello script) (vedi Esempio 14-9). Ha lo stesso effetto della lettura del valore della variabile builtin $PWD.

pushd, popd, dirs

Questa serie di comandi forma un sistema per tenere nota delle directory di lavoro; un mezzo per spostarsi avanti e indietro tra le directory in modo ordinato. Viene usato uno stack (del tipo LIFO) per tenere traccia dei nomi delle directory. Diverse opzioni consentono varie manipolazioni dello stack delle directory.

pushd nome-dir immette il percorso di nome-dir nello stack delle directory e simultaneamente passa dalla directory di lavoro corrente a nome-dir

popd preleva (pop) il nome ed il percorso della directory che si trova nella locazione più alta dello stack delle directory e contemporaneamente passa dalla directory di lavoro corrente a quella prelevata dallo stack.

dirs elenca il contenuto dello stack delle directory (lo si confronti con la variabile $DIRSTACK). Un comando pushd o popd, che ha avuto successo, invoca in modo automatico dirs.

Gli script che necessitano di ricorrenti cambiamenti delle directory di lavoro possono trarre giovamento dall'uso di questi comandi, evitando di dover codificare ogni modifica all'interno dello script. È da notare che nell'array implicito $DIRSTACK, accessibile da uno script, è memorizzato il contenuto dello stack delle directory.

Esempio 14-9. Cambiare la directory di lavoro corrente

#!/bin/bash

dir1=/usr/local
dir2=/var/spool

pushd $dir1
#  Viene eseguito un 'dirs' automatico (visualizza lo stack delle
#+ directory allo stdout).
echo "Ora sei nella directory `pwd`." #  Uso degli apici singoli
                                      #+ inversi per 'pwd'.

# Ora si fa qualcosa nella directory 'dir1'.
pushd $dir2
echo "Ora sei nella directory `pwd`."

# Adesso si fa qualcos'altro nella directory 'dir2'.
echo "Nella posizione più alta dell'array DIRSTACK si trova $DIRSTACK."
popd
echo "Sei ritornato alla directory `pwd`."

# Ora si fa qualche altra cosa nella directory 'dir1'.
popd
echo "Sei tornato alla directory di lavoro originaria `pwd`."

exit 0

# Cosa succede se non eseguite 'popd' -- prima di uscire dallo script?
# In quale directory vi trovereste alla fine? Perché?

Variabili

let

Il comando let permette di eseguire le operazioni aritmetiche sulle variabili. In molti casi, opera come una versione meno complessa di expr.

Esempio 14-10. Facciamo fare a let qualche calcolo aritmetico.

#!/bin/bash

echo

let a=11            # Uguale a 'a=11'
let a=a+5           # Equivale a  let "a = a + 5"
                    # (i doppi apici e gli spazi la rendono più leggibile.)
echo "11 + 5 = $a"  # 16

let "a <<= 3"       # Equivale a  let "a = a << 3"
echo "\"\$a\" (=16) scorrimento a sinistra di 3 bit = $a"
                    # 128

let "a /= 4"        # Equivale a  let "a = a / 4"
echo "128 / 4 = $a" # 32

let "a -= 5"        # Equivale a  let "a = a - 5"
echo "32 - 5 = $a"  # 27

let "a = a * 10"    # Equivale a  let "a = a * 10"
echo "27 * 10 = $a" # 270

let "a %= 8"        # Equivale a  let "a = a % 8"
echo "270 modulo 8 = $a  (270 / 8 = 33, resto $a)"
                    # 6

echo

exit 0
eval

eval arg1 [arg2] ... [argN]

Combina gli argomenti presenti in un'espressione, o in una lista di espressioni, e li valuta. Espande qualsiasi variabile presente nell'espressione. Il risultato viene tradotto in un comando. Può essere utile per generare del codice da riga di comando o da uno script.

bash$ processo=xterm
bash$ mostra_processo="eval ps ax | grep $processo"
bash$ $mostra_processo
1867 tty1     S      0:02 xterm
2779 tty1     S      0:00 xterm
2886 pts/1    S      0:00 grep xterm
	      

Esempio 14-11. Dimostrazione degli effetti di eval

#!/bin/bash

y=`eval ls -l`  #  Simile a  y=`ls -l`
echo $y         #+ ma con i ritorni a capo tolti perché la variabile
                #+ "visualizzata" è senza "quoting".
echo
echo "$y"       #  I ritorni a capo vengono mantenuti con il
                #+ "quoting" della variabile.

echo; echo

y=`eval df`     #  Simile a y=`df`
echo $y         #+ ma senza ritorni a capo.

#  Se non si preservano i ritorni a capo, la verifica dell'output
#+ con utility come "awk" risulta più facile.

echo
echo "======================================================================="
echo

# Ora vediamo come "espandere" una variabile usando "eval" . . .

for i in 1 2 3 4 5; do
  eval valore=$i
  #  valore=$i ha lo stesso effetto. "eval", in questo caso, non è necessario.
  #  Una variabile senza meta-significato valuta se stessa --
  #+ non può espandersi a nient'altro che al proprio contenuto letterale.
  echo $valore
done

echo
echo "---"
echo

for i in ls df; do
  valore=eval $i
  #  valore=$i in questo caso avrebbe un effetto completamente diverso.
  #  "eval" valuta i comandi "ls" e "df" . . .
  #  I termini "ls" e "df" hanno un meta-significato,
  #+ dal momento che sono interpretati come comandi
  #+ e non come stringhe di caratteri.
  echo $valore
done


exit 0

Esempio 14-12. Forzare un log-off

#!/bin/bash
Terminare ppp per forzare uno scollegamento.

Lo script deve essere eseguito da root.

terminappp="eval kill -9 `ps ax | awk '/ppp/ { print $1 }'`"
#                        ----- ID di processo di ppp ------

$terminappp             # La variabile è diventata un comando.


# Le operazioni seguenti devono essere eseguite da root.

chmod 666 /dev/ttyS3    #  Ripristino dei permessi di lettura+scrittura, 
                        #+ altrimenti?
#  Quando si invia un SIGKILL a ppp i permessi della porta seriale vengono
#+ modificati, quindi vanno ripristinati allo stato precedente il SIGKILL.

rm /var/lock/LCK..ttyS3 # Cancella il lock file della porta seriale. Perché?

exit 0

# Esercizi:
# --------
# 1) Lo script deve verificare se è stato root ad invocarlo.
# 2) Effettuate un controllo per verificare che, prima di tentarne la chiusura,
#+   il processo che deve essere terminato sia effettivamente in esecuzione.
# 3) Scrivete una versione alternativa dello script basata su 'fuser':
#+   if [ fuser -s /dev/modem ]; then . . .

Esempio 14-13. Una versione di rot13

#!/bin/bash
# Una versione di "rot13" usando 'eval'.
# Confrontatelo con l'esempio "rot13.sh".

impvar_rot_13()              # Codifica "rot13"
{
  local nomevar=$1 valoreval=$2
  eval $nomevar='$(echo "$valoreval" | tr a-z n-za-m)'
}


impvar_rot_13 var "foobar"   # Codifica "foobar" con rot13.
echo $var                    # sbbone

impvar_rot_13 var "$var"     # Codifica "sbbone" con rot13.
                             # Ritorno al valore originario della variabile.
echo $var                    # foobar

# Esempio di Stephane Chazelas.
# Modificato dall'autore del documento.

exit 0

Rory Winston ha fornito il seguente esempio che dimostra quanto possa essere utile eval.

Esempio 14-14. Utilizzare eval per forzare una sostituzione di variabile in uno script Perl

Nello script Perl  "test.pl":
        ...
        my $WEBROOT = <WEBROOT_PATH>;
        ...

Per forzare la sostituzione di variabile provate:
        $export WEBROOT_PATH=/usr/local/webroot
        $sed 's/<WEBROOT_PATH>/$WEBROOT_PATH/' < test.pl > out

Ma questo dà solamente:
        my $WEBROOT = $WEBROOT_PATH;

Tuttavia:
        $export WEBROOT_PATH=/usr/local/webroot
        $eval sed 's%\<WEBROOT_PATH\>%$WEBROOT_PATH%' < test.pl > out
#        ====

Che funziona bene, eseguendo l'attesa sostituzione:
        my $WEBROOT = /usr/local/webroot;


### Correzioni all'esempio originale eseguite da Paulo Marcel Coelho Aragao.

Attenzione

Il comando eval può essere rischioso e normalmente, quando esistono alternative ragionevoli, dovrebbe essere evitato. Un eval $COMANDI esegue tutto il contenuto di COMANDI, che potrebbe riservare spiacevoli sorprese come un rm -rf *. Eseguire del codice non molto familiare contenente un eval, e magari scritto da persone sconosciute, significa vivere pericolosamente.

set

Il comando set modifica il valore delle variabili interne di uno script. Un possibile uso è quello di attivare/disattivare le modalità (opzioni), legate al funzionamento della shell, che determinano il comportamento dello script. Un'altra applicazione è quella di reimpostare i parametri posizionali passati ad uno script con il risultato dell'istruzione (set `comando`). Lo script assume i campi dell'output di comando come parametri posizionali.

Esempio 14-15. Utilizzare set con i parametri posizionali

#!/bin/bash

# script "set-test"

# Invocate lo script con tre argomenti da riga di comando,
# per esempio, "./set-test uno due tre".

echo
echo "Parametri posizionali prima di  set \`uname -a\` :"
echo "Argomento nr.1 da riga di comando = $1"
echo "Argomento nr.2 da riga di comando = $2"
echo "Argomento nr.3 da riga di comando = $3"


set `uname -a`  # Imposta i parametri posizionali all'output
                # del comando `uname -a`

echo $_         # Sconosciuto
# Opzioni impostate nello script.

echo "Parametri posizionali dopo  set \`uname -a\` :"
# $1, $2, $3, ecc. reinizializzati col risultato di `uname -a`
echo "Campo nr.1 di 'uname -a' = $1"
echo "Campo nr.2 di 'uname -a' = $2"
echo "Campo nr.3 di 'uname -a' = $3"
echo ---
echo $_         # ---
echo

exit 0

Divertirsi ancora con i parametri posizionali.

Esempio 14-16. Invertire i parametri posizionali

#!/bin/bash
# revposparams.sh: inverte i parametri posizionali.
# Script di Dan Jacobson, con revisioni stilistiche dell'autore del documento.


set a\ b c d\ e;
#     ^      ^     Spazi con escaping
#       ^ ^        Spazi senza escaping
OIFS=$IFS; IFS=:;
#              ^   Salva il precedente IFS e imposta quello nuovo.

echo

until [ $# -eq 0 ]
do          #      Scorre i parametri posizionali.
  echo "### k0 = "$k""     # Prima
  k=$1:$k;  #      Accoda ciascun parametro nella variabile del ciclo.
#     ^
  echo "### k = "$k""      # Dopo
  echo
  shift;
done

set $k  #  Imposta i nuovi parametri posizionali.
echo -
echo $# #  Conteggio dei parametri posizionali.
echo -
echo

for i   #  Omettendo "in lista" si imposta la variabile  -- i --
        #+ ai parametri posizionali.
do
  echo $i  # Visualizza i nuovi parametri posizionali.
done

IFS=$OIFS  # Ripristina IFS.

#  Domande:
#  È necessario impostare un nuovo IFS, internal field separator,
#+ perché lo script funzioni correttamente?
#  Cosa succede se non viene fatto? Provate.
#  E perché usare un nuovo IFS -- i due punti -- alla riga 17,
#+ per l'accodamento nella variabile del ciclo?
#  Qual'è il suo scopo?

exit 0

$ ./revposparams.sh

### k0 = 
### k = a b

### k0 = a b
### k = c a b

### k0 = c a b
### k = d e c a b

-
3
-

d e
c
a b

Invocando set senza alcuna opzione, o argomento, viene visualizzato semplicemente l'elenco di tutte le variabili d'ambiente, e non solo, che sono state inizializzate.

bash$ set
AUTHORCOPY=/home/bozo/posts
BASH=/bin/bash
BASH_VERSION=$'2.05.8(1)-release'
...
XAUTHORITY=/home/bozo/.Xauthority
_=/etc/bashrc
variabile22=abc
variabile23=xzy
	      

set con l'opzione -- assegna in modo esplicito il contenuto della variabile ai parametri posizionali. Se non viene specificata nessuna variabile dopo --, i parametri posizionali vengono annullati.

Esempio 14-17. Riassegnare i parametri posizionali

#!/bin/bash

variabile="uno due tre quattro cinque"

set -- $variabile
# Imposta i parametri posizionali al contenuto di "$variabile".

primo_param=$1
secondo_param=$2
shift; shift        # Salta i primi due parametri posizionali.
# Funziona anche    shift 2           
restanti_param="$*"

echo
echo "primo parametro = $primo_param"             # uno
echo "secondo parametro = $secondo_param"         # due
echo "rimanenti parametri = $restanti_param"      # tre quattro cinque

echo; echo

# Ancora.
set -- $variabile
primo_param=$1
secondo_param=$2
echo "primo parametro = $primo_param"             # uno
echo "secondo parametro = $secondo_param"         # due

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

set --
#  Annulla i parametri posizionali quando non viene specificata
#+ nessuna variabile.

primo_param=$1
secondo_param=$2
echo "primo parametro = $primo_param"             # (valore nullo)
echo "secondo parametro = $secondo_param"         # (valore nullo)

exit 0

Vedi anche Esempio 10-2 e Esempio 15-51.

unset

il comando unset annulla una variabile di shell, vale a dire, la imposta al valore nullo. Fate attenzione che questo comando non è applicabile ai parametri posizionali.

bash$ unset PATH

bash$ echo $PATH

bash$ 

Esempio 14-18. "Annullare" una variabile

#!/bin/bash
# unset.sh: Annullare una variabile.

variabile=ciao                             # Inizializzata.
echo "variabile = $variabile"

unset variabile                            # Annullata.
                                           # Stesso effetto di: variabile=
echo "variabile (annullata) = $variabile"  # $variabile è nulla.

if [ -z "$variabile" ]                     #  Prova una verifica di lunghezza 
                                           #+ della stringa.
then
  echo "\$variabile ha lunghezza zero."
fi

exit 0
export

Il comando export rende disponibili le variabili a tutti i processi figli generati dallo script in esecuzione o dalla shell. Purtroppo, non vi è alcun modo per esportare le variabili in senso contrario verso il processo genitore, ovvero nei confronti del processo che ha chiamato o invocato lo script o la shell. Un uso importante del comando export si trova nei file di avvio (startup) per inizializzare e rendere accessibili le variabili d'ambiente ai susseguenti processi utente.

Esempio 14-19. Utilizzare export per passare una variabile ad uno script awk incorporato

#!/bin/bash

#  Ancora un'altra versione dello script "column totaler" 
#+ (col-totaler.sh) che aggiunge una specifica colonna (di numeri)
#+ nel file di destinazione. Qui viene usato l'ambiente per passare
#+ una variabile dello script ad 'awk'... e inserire lo script awk
#+ in una variabile.

ARG=2
E_ERR_ARG=65

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

nomefile=$1
colonna_numero=$2

#===== Fino a questo punto è uguale allo script originale =====#

export colonna_numero
#  Esporta il numero di colonna all'ambiente, in modo che sia disponibile
#+ all'utilizzo.


# ------------------------------------------------
scriptawk='{ totale += $ENVIRON["colonna_numero"]}
END { print totale }'
# Sì, una variabile può contenere uno script awk.
# ------------------------------------------------

# Ora viene eseguito lo script awk.
awk $scriptawk $nomefile

# Grazie, Stephane Chazelas.

exit 0

Suggerimento

È possibile inizializzare ed esportare variabili con un'unica operazione, come export var1=xxx.

Tuttavia, come ha sottolineato Greg Keraunen, in certe situazioni questo può avere un effetto diverso da quello che si avrebbe impostando prima la variabile ed esportandola successivamente.

bash$ export var=(a b); echo ${var[0]}
(a b)



bash$ var=(a b); export var; echo ${var[0]}
a
	      

declare, typeset

I comandi declare e typeset specificano e/o limitano le proprietà delle variabili.

readonly

Come declare -r, imposta una variabile in sola lettura ovvero, in realtà, come una costante. I tentativi per modificare la variabile falliscono generando un messaggio d'errore. È l'analogo shell del qualificatore di tipo const del linguaggio C.

getopts

Questo potente strumento verifica gli argomenti passati da riga di comando allo script. È l'analogo Bash del comando esterno getopt e della funzione di libreria getopt familiare ai programmatori in C. Permette di passare e concatenare più opzioni [2] e argomenti associati allo script (per esempio nomescript -abc -e /usr/local).

Il costrutto getopts utilizza due variabili implicite. $OPTIND, che è il puntatore all'argomento, (OPTion INDex) e $OPTARG (OPTion ARGument) l'argomento (eventuale) associato ad un'opzione. Nella dichiarazione, i due punti che seguono il nome dell'opzione servono ad indicare che quell'opzione ha associato un argomento.

Il costrutto getopts di solito si trova all'interno di un ciclo while che elabora le opzioni e gli argomenti uno alla volta e quindi incrementa la variabile implicita $OPTIND per il passo successivo.

Nota

  1. Gli argomenti passati allo script da riga di comando devono essere preceduti da un meno (-). È il prefisso - che consente a getopts di riconoscere gli argomenti da riga di comando come opzioni. Infatti, getopts non elabora argomenti che non siano preceduti da - e termina la sua azione appena incontra un'opzione che ne è priva.

  2. La struttura di getopts differisce leggermente da un normale ciclo while perché non è presente la condizione di verifica.

  3. Il costrutto getopts sostituisce il deprecato comando esterno getopt.

while getopts ":abcde:fg" Opzione
# Dichiarazione iniziale.
# a, b, c, d, e, f, g sono le opzioni attese.
# I : dopo l'opzione 'e' indicano che c'è un argomento associato.
do
  case $Opzione in
  a ) # Fa qualcosa con la variabile 'a'.
  b ) # Fa qualcosa con la variabile 'b'.
  ...
  e)  # Fa qualcosa con 'e', e anche con $OPTARG,
      # che è l'argomento associato all'opzione 'e'.
  ...
  g ) # Fa qualcosa con la variabile 'g'.
  esac
done
shift $(($OPTIND - 1))
# Sposta il puntatore all'argomento successivo.

# Tutto questo non è affatto complicato come sembra <sorriso>.
	      

Esempio 14-20. Utilizzare getopts per leggere le opzioni/argomenti passati ad uno script

#!/bin/bash
# Prove con getopts e OPTIND
# Script modificato il 9/10/03 su suggerimento di Bill Gradwohl.

#  Osserviamo come 'getopts' elabora gli argomenti passati allo script da 
#+ riga di comando.
#  Gli argomenti vengono verificati come "opzioni" (flag)
#+ ed argomenti associati.

#  Provate ad invocare lo script con
#  'nomescript -mn'
#  'nomescript -oq qOpzione' (qOpzione può essere una stringa qualsiasi.)
#  'nomescript -qXXX -r'
#
#  'nomescript -qr'    - Risultato inaspettato, considera "r"
#+ come l'argomento dell'opzione "q"
#  'nomescript -q -r'  - Risultato inaspettato, come prima.
#  'nomescript -mnop -mnop'  - Risultato inaspettato
#  (OPTIND non è attendibile nello stabilire da dove proviene un'opzione).
#
#  Se un'opzione si aspetta un argomento ("flag:"), viene presa
#  qualunque cosa si trovi vicino.

NO_ARG=0
E_ERR_OPZ=65

if [ $# -eq "$NO_ARG" ]   #  Lo script è stato invocato senza
                          #+ alcun argomento?
then
  echo "Utilizzo: `basename $0` opzioni (-mnopqrs)"
  exit $E_ERR_OPZ         #  Se non ci sono argomenti, esce e
                          #+ spiega come usare lo script.
fi
# Utilizzo: nomescript -opzioni
# Nota: è necessario il trattino (-)


while getopts ":mnopq:rs" Opzione
do
  case $Opzione in
    m     ) echo "Scenario nr.1: opzione -m-  [OPTIND=${OPTIND}]";;
    n | o ) echo "Scenario nr.2: opzione -$Opzione-  [OPTIND=${OPTIND}]";;
    p     ) echo "Scenario nr.3: opzione -p-  [OPTIND=${OPTIND}]";;
    q     ) echo "Scenario nr.4: opzione -q-\
con argomento \"$OPTARG\"  [OPTIND=${OPTIND}]";;
    # Notate che l'opzione 'q' deve avere un argomento associato,
    # altrimenti salta alla voce predefinita del costrutto case.
    r | s ) echo "Scenario nr.5: opzione -$Opzione-"'';;
    *     ) echo "È stata scelta un'opzione non implementata.";; # DEFAULT
  esac
done

shift $(($OPTIND - 1))
#  Decrementa il puntatore agli argomenti in modo che punti al successivo.
#  $1 fa ora riferimento al primo elemento non-opzione fornito da riga di
#+ comando, ammesso che ci sia.

exit 0

#   Come asserisce Bill Gradwohl,
#  "Il funzionamento di getopts permette di specificare: nomescript -mnop -mnop,
#+  ma non esiste, utilizzando OPTIND, nessun modo affidabile per differenziare
#+  da dove proviene che cosa."

Comportamento dello Script

source, . (comando punto )

Questa istruzione, se invocata da riga di comando, esegue uno script. All'interno di uno script, source nome-file carica il file nome-file. Caricando un file (comando-punto) si importa codice all'interno dello script, accodandolo (stesso effetto della direttiva #include di un programma C ). Il risultato finale è uguale all'"inserimento" di righe di codice nel corpo dello script. È utile in situazioni in cui diversi script usano un file dati comune o una stessa libreria di funzioni.

Esempio 14-21. "Includere" un file dati

#!/bin/bash

. file-dati    # Carica un file dati.
# Stesso effetto di "source file-dati", ma più portabile.

#  Il file "file-dati" deve essere presente nella directory di lavoro
#+ corrente, poiché vi si fa riferimento per mezzo del suo 'basename'.

# Ora utilizziamo alcuni dati del file.

echo "variabile1 (dal file-dati) = $variabile1"
echo "variabile3 (dal file-dati) = $variabile3"

let "sommma = $variabile2 + $variabile4"
echo "Somma della variabile2 + variabile4 (dal file-dati) = $somma"
echo "messaggio1 (dal file-dati)  \"$messaggio1\""
# Nota:                            apici doppi con escape.

visualizza_messaggio Questa è la funzione di visualizzazione messaggio \
presente in file-dati.


exit 0

Il file file-dati per l'Esempio 14-21 precedente. Dev'essere presente nella stessa directory.

#  Questo è il file dati caricato dallo script.
#  File di questo tipo possono contenere variabili, funzioni, ecc.
#  Può essere caricato con il comando 'source' o '.' da uno script di shell.

# Inizializziamo alcune variabili.

variabile1=22
variabile2=474
variabile3=5
variabile4=97

messaggio1="Ciao, come stai?"
messaggio2="Per ora piuttosto bene. Arrivederci."

visualizza_messaggio ()
{
# Visualizza qualsiasi messaggio passato come argomento.

  if [ -z "$1" ]
  then
  return 1
  # Errore, se l'argomento è assente.
  fi

  echo

  until [ -z "$1" ]
  do
  # Scorre gli argomenti passati alla funzione.
    echo -n "$1"
    #  Visualizza gli argomenti uno alla volta, eliminando i ritorni a capo.
    echo -n " "
    # Inserisce degli spazi tra le parole.
    shift
    # Successivo.
  done

  echo

  return 0
}  

Se il file caricato con source è anch'esso uno script eseguibile, verrà messo in esecuzione e, alla fine, il controllo ritornerà allo script che l'ha richiamato. A questo scopo, uno script eseguibile caricato con source può usare return.

Si possono passare (opzionalmente) degli argomenti al file caricato con source come parametri posizionali.

source $nomefile $arg1 arg2

È anche possibile per uno script usare source in riferimento a se stesso, sebbene questo non sembri avere delle applicazioni pratiche.

Esempio 14-22. Un (inutile) script che "carica" se stesso

#!/bin/bash
# self-source.sh: uno script che segue se stesso "ricorsivamente."
# Da "Stupid Script Tricks," Volume II.

MAXPASSCNT=100    # Numero massimo di esecuzioni.

echo -n  "$conta_passi  "
#  Al primo passaggio, vengono visualizzati solo due spazi, 
#+ perché $conta_passi non è stata inizializzata.

let "conta_passi += 1"
#  Si assume che la variabile $conta_passi non inizializzata possa essere 
#+ incrementata subito.
#  Questo funziona con Bash e pdksh, ma si basa su un'azione non portabile 
#+ (e perfino pericolosa).
#  Sarebbe meglio impostare $conta_passi a 0 prima che venga incrementata.

while [ "$conta_passi" -le $MAXPASSCNT ]
do
  . $0   #  Lo script "esegue" se stesso, non chiama se stesso.
         #  ./$0 (che sarebbe la vera ricorsività) in questo caso non funziona.
         #  Perché?
done

#  Quello che avviene in questo script non è una vera ricorsività, perché lo
#+ script in realtà "espande" se stesso, vale a dire genera una nuova 
#+ sezione di codice ad ogni passaggio attraverso il ciclo 'while', 
#+ con ogni 'source' che si trova alla riga 20.
#
#  Naturalmente, lo script interpreta ogni succesiva 'esecuzione' della riga 
#+ con "#!" come un commento e non come l'inizio di un nuovo script.

echo

exit 0   # Il risultato finale è un conteggio da 1 a 100.
         # Molto impressionante.

# Esercizio:
# ---------
#  Scrivete uno script che usi questo espediente per fare qualcosa 
#+ di veramente utile.
exit

Termina in maniera incondizionata uno script. [3] Il comando exit opzionalmente può avere come argomento un intero che viene restituito alla shell come exit status dello script. È buona pratica terminare tutti gli script, tranne quelli più semplici, con exit 0, indicandone con ciò la corretta esecuzione.

Nota

Se uno script termina con un exit senza argomento, l'exit status dello script corrisponde a quello dell'ultimo comando eseguito nello script, escludendo exit. Equivale a exit $?.

Nota

Il comando exit può anche essere usato per terminare una subshell.

exec

Questo builtin di shell sostituisce il processo corrente con un comando specificato. Normalmente, quando la shell incontra un comando, genera (forking) un processo figlio che è quello che esegue effettivamente il comando. Utilizzando il builtin exec, la shell non esegue il forking ed il comando lanciato con exec sostituisce la shell. Se viene usato in uno script, quindi, ne forza l'uscita quando il comando eseguito con exec termina. [4]

Esempio 14-23. Effetti di exec

#!/bin/bash

exec echo "Uscita da \"$0\"." # Esce dallo script in questo punto.

# --------------------------------------------
# Le righe seguenti non verranno mai eseguite.

echo "Questo messaggio non verrà mai visualizzato."

exit 99          #  Questo script non termina qui.
                 #  Verificate l'exit status, dopo che lo script è 
                 #+ terminato, con 'echo $?'.
                 #  *Non* sarà 99.

Esempio 14-24. Uno script che esegue se stesso con exec

#!/bin/bash
# self-exec.sh

echo

echo "Sebbene questa riga compaia UNA SOLA VOLTA nello script, continuerà"
echo "ad essere visualizzata."
echo "Il PID di questo script d'esempio è ancora $$."
#     Dimostra che non viene generata una subshell.

echo "==================== Premi Ctl-C per uscire ===================="

sleep 1

exec $0   #  Inizia un'altra istanza di questo stesso script
          #+ che sostituisce quella precedente.

echo "Questa riga non verrà mai visualizzata"  # Perché?

exit 0

exec serve anche per riassegnare i descrittori dei file. Per esempio, exec <zzz-file sostituisce lo stdin con il file zzz-file.

Nota

L'opzione -exec di find non è la stessa cosa del builtin di shell exec.

shopt

Questo comando permette di cambiare le opzioni di shell al volo (vedi Esempio 24-1 e Esempio 24-2). Appare spesso nei file di avvio (startup) Bash, ma può essere usato anche in altri script. È necessaria la versione 2 o seguenti di Bash.

shopt -s cdspell
#  Consente le errate digitazioni, non gravi, dei nomi delle directory quando 
#+ si usa 'cd'

cd /hpme  # Oops! Errore '/home'.
pwd       # /home
          # La shell ha corretto l'errore di digitazione.

caller

Inserendo il comando caller all'interno di una funzione vengono visualizzate allo stdout informazioni su chi ha richiamato quella funzione.

#!/bin/bash

funzione1 ()
{
  # All'interno di funzione1 ().
  caller 0   # Dimmi tutto.
}

funzione1    # Riga 9 dello script.

# 9 main test.sh
# ^                 Numero della riga dove la funzione è stata richiamata.
#   ^^^^            Invocata dalla parte "main" dello script.
#        ^^^^^^^    Nome dello script chiamante.

caller 0     # Non ha alcun effetto perché non è in una funzione.

Il comando caller può restituire anche informazioni sul chiamante se inserita uno script caricato con source all'interno di un altro script. Come una funzione, si tratta di una "chiamata di subroutine."

Questo comando potrebbe essere utile nel debugging.

Comandi

true

Comando che restituisce zero come exit status di una corretta esecuzione, ma nient'altro.

bash$ true
bash$ echo $?
0
	    

# Ciclo infinito
while true   # alternativa a ":"
do
   operazione-1
   operazione-2
   ...
   operazione-n
   # Occorre un sistema per uscire dal ciclo, altrimenti lo script si blocca.
done

false

Comando che restituisce l'exit status di una esecuzione non andata a buon fine, ma nient'altro.

bash$ false
bash$ echo $?
1
	     

Prova di "false" 
if false
then
  echo "false valuta \"vero\""
else
  echo "false valuta \"falso\""
fi
# false valuta "falso"

  
# Ciclo while "falso" (ciclo nullo)
while false
do
   # Il codice seguente non verrà eseguito.
   operazione-1
   operazione-2
   ...
   operazione-n
   # Non succede niente!
done

type [comando]

Simile al comando esterno which, type comando identifica "comando". A differenza di which, type è un builtin di Bash. L'utile opzione -a di type identifica le parole chiave ed i builtin, individuando anche i comandi di sistema che hanno gli stessi nomi.

bash$ type '['
[ is a shell builtin
bash$ type -a '['
[ is a shell builtin
[ is /usr/bin/[

bash$ type type
type is a shell builtin
	      

hash [comandi]

Registra i percorsi assoluti dei comandi specificati -- nella tabella degli hash della shell [5] -- in modo che la shell o lo script non avranno bisogno di cercare $PATH nelle successive chiamate di quei comandi. Se hash viene eseguito senza argomenti, elenca semplicemente i comandi presenti nella tabella. L'opzione -r cancella la tabella degli hash.

bind

Il builtin bind visualizza o modifica a configurazione d'uso della tastiera tramite readline [6].

help

Fornisce un breve riepilogo dell'utilizzo di un builtin di shell. È il corrispettivo di whatis, per i builtin.

bash$ help exit
exit: exit [n]
    Exit the shell with a status of N.  If N is omitted, the exit status
    is that of the last command executed.
	      

14.1. Comandi di controllo dei job

Alcuni dei seguenti comandi di controllo di job possono avere come argomento un "identificatore di job". Vedi la tabella alla fine del capitolo.

jobs

Elenca i job in esecuzione in background, fornendo il rispettivo numero. Non è così utile come ps.

Nota

È facilissimo confondere job e processi. Alcuni builtin, quali kill, disown e wait, accettano come argomento sia il numero di job che quello di processo. I comandi fg, bg e jobs accettano solo il numero di job.

bash$ sleep 100 &
[1] 1384

bash $ jobs
[1]+  Running                 sleep 100 &

"1" è il numero di job (i job sono gestiti dalla shell corrente), mentre "1384" è il numero di processo (i processi sono gestiti dal sistema operativo). Per terminare questo job/processo si può utilizzare sia kill %1 che kill 1384.

Grazie, S.C.

disown

Cancella il/i job dalla tabella dei job attivi della shell.

fg, bg

Il comando fg modifica l'esecuzione di un job da background (sfondo) in foreground (primo piano). Il comando bg fa ripartire un job che era stato sospeso, mettendolo in esecuzione in background. Se non viene specificato nessun numero di job, allora il comando fg o bg agisce sul job attualmente in esecuzione.

wait

Arresta l'esecuzione dello script finché tutti i job in esecuzione in background non sono terminati, o finché non è terminato il job o il processo il cui ID è stato passato come opzione. Restituisce l'exit status di attesa-comando.

Il comando wait può essere usato per evitare che uno script termini prima che un job in esecuzione in background abbia ultimato il suo compito (ciò creerebbe un temibile processo orfano).

Esempio 14-25. Attendere la fine di un processo prima di continuare

#!/bin/bash

ROOT_UID=0   # Solo gli utenti con $UID 0 posseggono i privilegi di root.
E_NONROOT=65
E_NOPARAM=66

if [ "$UID" -ne "$ROOT_UID" ]
then
  echo "Bisogna essere root per eseguire questo script."
  # "Cammina ragazzo, hai finito di poltrire."
  exit $E_NONROOT
fi

if [ -z "$1" ]
then
  echo "Utilizzo: `basename $0` nome-cercato"
  exit $E_NOPARAM
fi


echo "Aggiornamento del database 'locate' ..."
echo "Questo richiede un po' di tempo."
updatedb /usr &     # Deve essere eseguito come root.

wait
#  Non viene eseguita la parte restante dello script finché 'updatedb' non 
#+ ha terminato il proprio compito.
#  Si vuole che il database sia aggiornato prima di cercare un nome di file.

locate $1

#  Senza il comando wait, nell'ipotesi peggiore, lo script sarebbe uscito 
#+ mentre 'updatedb' era ancora in esecuzione, trasformandolo in un processo 
#+ orfano.

exit 0

Opzionalmente, wait può avere come argomento un identificatore di job, per esempio, wait%1 o wait $PPID. Vedi la tabella degli identificatori di job.

Suggerimento

In uno script, far eseguire un comando in background, per mezzo della E commerciale (&), può causare la sospensione dello script finché non viene premuto il tasto INVIO. Questo sembra capitare con i comandi che scrivono allo stdout. Può rappresentare un grande fastidio.

#!/bin/bash
# test.sh

ls -l &
echo "Fatto."
bash$ ./test.sh
Fatto.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
 _
               

Mettendo wait dopo il comando che deve essere eseguito in background si rimedia a questo comportamento.

#!/bin/bash
# test.sh		  

ls -l &
echo "Fatto."
wait
bash$ ./test.sh
Fatto.
 [bozo@localhost test-scripts]$ total 1
 -rwxr-xr-x    1 bozo     bozo           34 Oct 11 15:09 test.sh
               
Un altro modo per far fronte a questo problema è quello di redirigere l'output del comando in un file o anche in /dev/null.

suspend

Ha un effetto simile a Control-Z, ma sospende la shell (il processo genitore della shell può, ad un certo momento stabilito, farle riprendere l'esecuzione).

logout

È il comando di uscita da una shell di login. Facoltativamente può essere specificato un exit status.

times

Fornisce statistiche sul tempo di sistema impiegato per l'esecuzione dei comandi, nella forma seguente:

0m0.020s 0m0.020s

Questo comando ha un valore molto limitato perché non è di uso comune tracciare profili o benchmark degli script di shell.

kill

Termina immediatamente un processo inviandogli un appropriato segnale di terminazione (vedi Esempio 16-6).

Esempio 14-26. Uno script che uccide sé stesso

#!/bin/bash
# self-destruct.sh

kill $$  # Lo script in questo punto "uccide" il suo stesso processo.
         # Ricordo che "$$" è il PID dello script.

echo "Questa riga non viene visualizzata."
# Invece, la shell invia il messaggio "Terminated" allo stdout.

exit 0

#  Dopo che lo script è terminato prematuramente, qual'è l'exit 
#+ status restituito?
#
# sh self-destruct.sh
# echo $?
# 143
#
# 143 = 128 + 15
#             segnale SIGTERM

Nota

kill -l elenca tutti i segnali (come fa il file /usr/include/asm/signal.h). kill -9 è il "killer infallibile", che solitamente interrompe un processo che si rifiuta ostinatamente di terminare con un semplice kill. Talvolta funziona anche kill -15. Un "processo zombie", vale a dire un processo figlio che è stato terminato, ma il cui processo genitore non lo è stato (ancora), non può essere ucciso -- non si può uccidere qualcosa che è già morto. Comunque init, presto o tardi, solitamente lo cancellerà.

killall

Il comando killall termina un processo in esecuzione usando il nome invece che l'ID di processo. Se di un particolare comando sono in esecuzione istanze multiple, allora un killall su quel comando le terminarà tutte.

Nota

Qui si fa riferimento al comando killall in /usr/bin, non allo script killall presente in /etc/rc.d/init.d.

command

La direttiva command disabilita gli alias e le funzioni del comando che lo segue.

bash$ command ls
	  

Nota

È una delle tre direttive di shell attinenti all'elaborazione dei comandi di uno script. Le altre sono builtin ed enable.

builtin

Invocando builtin COMANDO_BUILTIN viene eseguito "COMANDO_BUILTIN" come se fosse un builtin di shell, disabilitando temporaneamente sia le funzioni che i comandi di sistema esterni aventi lo stesso nome.

enable

Abilita o disabilita un builtin di shell. Ad esempio, enable -n kill disabilita il builtin di shell kill, così quando Bash successivamente incontra un kill, invocherà /bin/kill.

L'opzione -a di enable elenca tutti i builtin di shell, indicando se sono abilitati o meno. L'opzione -f nomefile permette ad enable di caricare un builtin come un modulo di una libreria condivisa (DLL) da un file oggetto correttamente compilato. [7].

autoload

È un adattamento per Bash dell'autoloader ksh. In presenza di un autoload , viene caricata una funzione contenente una dichiarazione "autoload" da un file esterno, alla sua prima invocazione. [8] Questo fa risparmiare risorse di sistema.

È da notare che autoload non fa parte dell'installazione normale di Bash. Bisogna caricarlo con enable -f (vedi sopra).

Tabella 14-1. Identificatori di job

NotazioneSignificato
%Nnumero associato al job [N]
%SChiamata (da riga di comando) del job che inizia con la stringa S
%?SChiamata (da riga di comando) del job con al suo interno la stringa S
%%job "corrente" (ultimo job arrestato in foreground o iniziato in background)
%+job "corrente" (ultimo job arrestato in foreground o iniziato in background)
%-Ultimo job
$!Ultimo processo in background

Note

[1]

Un'eccezione è rappresentata dal comando time, citato nella documentazione ufficiale Bash come parola chiave.

[2]

Un'opzione è un argomento che funziona come un interruttore, attivando/disattivando le modalità di azione di uno script. L'argomento associato ad una particolare opzione ne indica o meno l'abilitazione.

[3]

Tecnicamente, exit termina solamente il processo (o la shell) in cui è in esecuzione, non il processo genitore.

[4]

Tranne quando exec viene usato per riassegnare i descrittori dei file.

[5]

L'hashing è un metodo per la creazione di chiavi di ricerca per i dati registrati in una tabella. Le chiavi vengono create "codificando" i dati stessi per mezzo di uno tra i numerosi e semplici algoritmi matematici (metodi, o formule).

Il vantaggio dell'hashing è la velocità. Lo svantaggio è che possono verificarsi delle "collisioni" -- quando una sola chiave fa riferimento a più di un dato.

Per esempi di hashing vedi Esempio A-21 e Esempio A-22.

[6]

La libreria readline è quella che Bash utilizza per leggere l'input in una shell interattiva.

[7]

I sorgenti C per un certo numero di builtin caricabili, solitamente, si trovano nella directory/usr/share/doc/bash-?.??/functions.

E' da notare che l'opzione -f di enable non è portabile su tutti i sistemi.

[8]

Lo stesso risultato di autoload può essere ottenuto con typeset -fu.