Capitolo 11. Sostituzione di comando

La sostituzione di comando riassegna il risultato di un comando [1], o anche di più comandi. Letteralmente: connette l'output di un comando ad un altro contesto. [2]

La forma classica di sostituzione di comando utilizza gli apici singoli inversi (`...`). I comandi all'interno degli apici inversi (apostrofi inversi) generano una riga di testo formata dai risultati dei comandi.

nome_script=`basename $0`
echo "Il nome di questo script è $nome_script."

I risultati dei comandi possono essere usati come argomenti per un altro comando, per impostare una variabile e anche per generare la lista degli argomenti in un ciclo for.

rm `cat nomefile`   # "nomefile" contiene l'elenco dei file da cancellare.
#
# S. C. fa notare che potrebbe ritornare l'errore "arg list too long".
# Meglio              xargs rm -- < nomefile
# ( -- serve nei casi in cui "nomefile" inizia con un "-" )
					  
elenco_filetesto=`ls *.txt`
#  La variabile contiene i nomi di tutti i file *.txt della directory
#+ di lavoro corrente.
echo $elenco_filetesto
					  
elenco_filetesto2=$(ls *.txt)  # Forma alternativa di sostituzione di comando.
echo $elenco_filetesto2
# Stesso risultato.
					  
# Un problema possibile, nell'inserire un elenco di file in un'unica stringa,
# è che si potrebbe insinuare un ritorno a capo.
#
#  Un modo più sicuro per assegnare un elenco di file ad un parametro
#+ è usare un array.
#      shopt -s nullglob       # Se non viene trovato niente, il nome del file
#+                               non viene espanso.
#      elenco_filetesto=( *.txt )
#
# Grazie, S.C.

Nota

La sostituzione di comando invoca una subshell.

Attenzione

La sostituzione di comando può dar luogo alla suddivisione delle parole.

COMANDO `echo a b`     # 2 argomenti: a e b;
						  
COMANDO "`echo a b`"   # 1 argomento: "a b"
						  
COMANDO `echo`         # nessun argomento
						  
COMANDO "`echo`"       # un argomento vuoto
						  
						  
# Grazie, S.C.

Anche quando la suddivisione delle parole non si verifica, la sostituzione di comando rimuove i ritorni a capo finali.

# cd "`pwd`"  # Questo dovrebbe funzionare sempre.
# Tuttavia...
						  
mkdir 'nome di directory con un carattere di a capo finale
'
						  
cd 'nome di directory con un carattere di a capo finale
'
						  
cd "`pwd`"  # Messaggio d'errore:
# bash: cd: /tmp/file with trailing newline: No such file or directory
						  
cd "$PWD"   # Funziona bene.
						  
						  
						  
						  
						  
precedenti_impostazioni_tty=$(stty -g)  #  Salva le precedenti impostazioni
					#+ del terminale.
echo "Premi un tasto "
stty -icanon -echo                      #  Disabilita la modalità 
					#+ "canonica" del terminale.
					#  Disabilita anche l'echo *locale*.
tasto=$(dd bs=1 count=1 2> /dev/null)   #  Uso di 'dd' per rilevare il
					#+ tasto premuto.
stty "$precedenti_impostazioni_tty"     #  Ripristina le vecchie impostazioni.
echo "Hai premuto ${#tasto} tasto/i."   #  ${#variabile} = numero di caratteri
					#+ in $variabile
#
# Premete qualsiasi tasto tranne INVIO, l'output sarà "Hai premuto 1 tasto/i."
# Premete INVIO e sarà "Hai premuto 0 tasto/i."
# Nella sostituzione di comando i ritorni a capo vengono eliminati.
						  
Grazie, S.C.

Attenzione

L'uso di echo per visualizzare una variabile senza quoting, e che è stata impostata con la sostituzione di comando, cancella i caratteri di ritorno a capo dall'output del/dei comando/i riassegnati. Questo può provocare spiacevoli sorprese.

elenco_directory=`ls -l`
echo $elenco_directory     # senza quoting
						  
# Ci si potrebbe aspettare un elenco di directory ben ordinato.
						  
# Invece, quello che si ottiene è:
# total 3 -rw-rw-r-- 1 bozo bozo 30 May 13 17:15 1.txt -rw-rw-r-- 1 bozo
# bozo 51 May 15 20:57 t2.sh -rwxr-xr-x 1 bozo bozo 217 Mar 5 21:13 wi.sh
						  
# I ritorni a capo sono scomparsi.
						  
						  
echo "$elenco_directory"   # con il quoting
# -rw-rw-r--    1 bozo       30 May 13 17:15 1.txt
# -rw-rw-r--    1 bozo       51 May 15 20:57 t2.sh
# -rwxr-xr-x    1 bozo      217 Mar  5 21:13 wi.sh

La sostituzione di comando consente anche di impostare una variabile al contenuto di un file, sia usando la redirezione che con il comando cat.

variabile1=`<file1`      #  Imposta "variabile1" al contenuto di "file1".
variabile2=`cat file2`   #  Imposta "variabile2" al contenuto di "file2".
			 #  Questo, tuttavia, genera un nuovo processo, quindi
			 #+ questa riga di codice viene eseguita più
			 #+ lentamente della precedente.
						  
#  Nota:
#  Le variabili potrebbero contenere degli spazi,
#+ o addirittura (orrore) caratteri di controllo.

#  Frammenti scelti dal file di sistema /etc/rc.d/rc.sysinit
#+ (su un'installazione Red Hat Linux)
						  
						  
 if [ -f /fsckoptions ]; then
       fsckoptions=`cat /fsckoptions`
 ...
fi
#
#
if [ -e "/proc/ide/${disk[$device]}/media" ] ; \
       then hdmedia=`cat /proc/ide/${disk[$device]}/media`
...
fi
#
#
if [ ! -n "`uname -r | grep -- "-"`" ]; then
       ktag="`cat /proc/version`"
...
fi
#
#
if [ $usb = "1" ]; then
       sleep 5
       mouseoutput=`cat /proc/bus/usb/devices \
2>/dev/null|grep -E "^I.*Cls=03.*Prot=02"`
       kbdoutput=`cat /proc/bus/usb/devices \
2>/dev/null|grep -E "^I.*Cls=03.*Prot=01"`
...
fi

Attenzione

Non si imposti una variabile con il contenuto di un file di testo di grandi dimensioni, a meno che non si abbia una ragione veramente buona per farlo. Non si imposti una variabile con il contenuto di un file binario, neanche per scherzo.

Esempio 11-1. Stupid script tricks

#!/bin/bash
# stupid-script-tricks.sh: Gente, non eseguitelo!
# Da "Stupid Script Tricks," Volume I.


variabile_pericolosa=`cat /boot/vmlinuz`   # Il kernel Linux compresso.

echo "Lunghezza della stringa \$variabile_pericolosa = ${#variabile_pericolosa}"
# Lunghezza della stringa $variabile_pericolosa = 794151
# (Non dà lo stesso risultato di 'wc -c /boot/vmlinuz'.)

# echo "$variabile_pericolosa"
# Non fatelo! Bloccherebbe l'esecuzione dello script.


#  L'autore di questo documento vi ha informato sull'assoluta inutilità delle
#+ applicazioni che impostano una variabile al contenuto di un file binario.

exit 0

È da notare che in uno script non si verifica un buffer overrun. Questo è un esempio di come un linguaggio interpretato, qual'è Bash, possa fornire una maggiore protezione dagli errori del programmatore rispetto ad un linguaggio compilato.

La sostituzione di comando consente di impostare una variabile con l'output di un ciclo. La chiave per far ciò consiste nel racchiudere l'output del comando echo all'interno del ciclo.

Esempio 11-2. Generare una variabile da un ciclo

#!/bin/bash
# csubloop.sh: Impostazione di una variabile all'output di un ciclo.

variabile1=`for i in 1 2 3 4 5
do
  echo -n "$i"                   #  In questo caso il comando 'echo' è
done`                            #+ cruciale nella sostituzione di comando.

echo "variabile1 = $variabile1"  # variabile1 = 12345


i=0
variabile2=`while [ "$i" -lt 10 ]
do
  echo -n "$i"                   # Ancora, il necessario 'echo'.
  let "i += 1"                   # Incremento.
done`

echo "variabile2 = $variabile2"  # variabile2 = 0123456789

#  E' dimostrato che �possibile inserire un ciclo
#+ in una dichiarazione di variabile.

exit 0

Nota

Nella sostituzione di comando, la forma $(COMANDO) ha preso il posto di quella con gli apostrofi inversi.

output=$(sed -n /"$1"/p $file) # Dall'esempio "grp.sh".
						  
# Impostazione di una variabile al contenuto di un file di testo.
Contenuto_file1=$(cat $file1)
Contenuto_file2=$(<$file2)     # Bash consente anche questa forma.

La sostituzione di comando nella forma $(...) gestisce la doppia barra inversa in modo diverso che non nella forma `...`.

bash$ echo `echo \\`

							  
bash$ echo $(echo \\)
\
	

La sostituzione di comando nella forma $(...) consente l'annidamento. [3]

conteggio_parole=$( wc -w $(ls -l | awk '{print $9}') )

O, qualcosa di più elaborato . . .

Esempio 11-3. Trovare anagrammi

#!/bin/bash
# agram2.sh
# Esempio di sostituzione di comando annidata.

#  Usa l'utility "anagram" che fa parte del pacchetto 
#+ dizionario "yawl" dell'autore di questo documento.
#  http://ibiblio.org/pub/Linux/libs/yawl-0.3.2.tar.gz
#  http://personal.riverusers.com/~thegrendel/yawl-0.3.2.tar.gz

E_NOARG=66
E_ERR_ARG=67
LUNMIN=7

if [ -z "$1" ]
then
  echo "Utilizzo $0 LETTERE"
  exit $E_NOARG          # Lo script vuole un argomento da riga di comando.
elif [ ${#1} -lt $LUNMIN ]
then
  echo "L'argomento deve essere formato da almento $LUNMIN lettere."
  exit $E_ERR_ARG
fi



FILTRO='.......'         # Formati almeno da 7 lettere.
#       1234567
Anagrammi=( $(echo $(anagram $1 | grep $FILTRO) ) )
#            |     |    sost. com. annidata   | |
#         (         assegnamento di array         )

echo
echo "Trovati ${#Anagrammi[*]} anagrammi di 7+ lettere"
echo
echo ${Anagrammi[0]}     # Primo anagramma.
echo ${Anagrammi[1]}     # Secondo anagramma.
                         # Ecc.

# echo "${Anagrammi[*]}" # Per elencare tutti gli anagrammi su un'unica riga . . .

#  Per chiarirvi le idee su quanto avviene in questo script
#+ controllate il capitolo "Array" più avanti.

# Per un altro esempio di ricerca di anagrammi, vedete lo script agram.sh.

exit $?

Esempi di sostituzione di comando negli script di shell:

  1. Esempio 10-7

  2. Esempio 10-26

  3. Esempio 9-29

  4. Esempio 15-3

  5. Esempio 15-19

  6. Esempio 15-15

  7. Esempio 15-49

  8. Esempio 10-13

  9. Esempio 10-10

  10. Esempio 15-29

  11. Esempio 19-8

  12. Esempio A-17

  13. Esempio 27-2

  14. Esempio 15-42

  15. Esempio 15-43

  16. Esempio 15-44

Note

[1]

Nella sostituzione di comando il comando può essere rappresentato da un comando di sistema, da un builtin interno a uno script o, addirittura, da una funzione dello script.

[2]

In senso tecnicamente più corretto, la sostituzione di comando estrae lo stdout di un comando riassegnandolo successivamente ad una variabile usando l'operatore =.

[3]

In realtà, l'annidamento è possibile anche con gli apostrofi inversi, effettuando, però, l'escaping di quelli interni, come ha evidenziato John Default.

conteggio_parole=` wc -w \`ls -l | awk '{print $9}'\` `