19.2. Redirigere blocchi di codice

Anche i blocchi di codice, come i cicli while, until e for, nonché i costrutti di verifica if/then, possono prevedere la redirezione dello stdin. Persino una funzione può usare questa forma di redirezione (vedi Esempio 23-11). È l'operatore <, posto alla fine del blocco, che svolge questo compito.

Esempio 19-5. Ciclo while rediretto

#!/bin/bash
# redir2.sh

if [ -z "$1" ]
then
  Nomefile=nomi.data        #  È il file predefinito, se non ne viene
                            #+ specificato alcuno.
else
  Nomefile=$1
fi
#+ Nomefile=${1:-nomi.data}
#  può sostituire la verifica precedente (sostituzione di parametro).

conto=0

echo

while [ "$nome" != Smith ]  #  Perché la variabile $nome è stata usata
                            #+ con il quoting?
do
  read nome                 # Legge da $Nomefile invece che dallo stdin.
  echo $nome
  let "conto += 1"
done <"$Nomefile"           # Redirige lo stdin nel file $Nomefile.
#    ^^^^^^^^^^^^

echo; echo "$conto nomi letti"; echo

exit 0

#  È da notare che, in alcuni linguaggi di scripting di shell più vecchi, il
#+ ciclo rediretto viene eseguito come una subshell.
#  Di conseguenza $conto restituirebbe 0, il valore di inizializzazione prima
#+ del ciclo.
#  Bash e ksh evitano l'esecuzione di una subshell "ogni qual volta questo sia
#+ possibile", cosicché questo script, ad esempio, funziona correttamente.
#  (Grazie a Heiner Steven per la precisazione.)

#  Tuttavia . . .
#  Bash, talvolta, *può* eseguire una subshell per un ciclo "while-read"
#+ posto dopo una PIPE, diversamente da un ciclo "while" REDIRETTO.

abc=hi
echo -e "1\n2\n3" | while read l
     do abc="$l"
        echo $abc
     done
echo $abc

#  Grazie a Bruno de Oliveira Schneider per averlo dimostrato
#+ con il precedente frammento di codice.
#  Grazie anche a Brian Onn per la correzione di un errore di notazione.

Esempio 19-6. Una forma alternativa di ciclo while rediretto

#!/bin/bash

# Questa è una forma alternativa dello script precedente.

#  Suggerito da Heiner Steven
#+ come espediente in quelle situazioni in cui un ciclo rediretto
#+ viene eseguito come subshell e, quindi, le variabili all'interno del ciclo
#+ non conservano i loro valori dopo che lo stesso è terminato.


if [ -z "$1" ]
then
  Nomefile=nomi.data      #  È il file predefinito, se non ne viene
                          #+ specificato alcuno.
else
  Nomefile=$1
fi  


exec 3<&0                   # Salva lo stdin nel descrittore di file 3.
exec 0<"$Nomefile"          # Redirige lo standard input.

conto=0
echo


while [ "$nome" != Smith ]
do
  read nome                 # Legge dallo stdin rediretto ($Nomefile).
  echo $nome
  let "conto += 1"
done                        #  Il ciclo legge dal file $Nomefile.
                            #+ a seguito dell'istruzione alla riga 21.

#  La versione originaria di questo script terminava il ciclo "while" con
#+      done <"$Nomefile"
#  Esercizio:
#  Perché questo non è più necessario?


exec 0<&3                   # Ripristina il precedente stdin.
exec 3<&-                   # Chiude il temporaneo df 3.

echo; echo "$conto nomi letti"; echo

exit 0

Esempio 19-7. Ciclo until rediretto

#!/bin/bash
# Uguale all'esempio precedente, ma con il ciclo "until".

if [ -z "$1" ]
then
  Nomefile=nomi.data          #  È il file predefinito, se non ne viene 
                              #+ specificato alcuno.
else
  Nomefile=$1
fi  

# while [ "$nome" != Smith ]
until [ "$nome" = Smith ]     # Il != è cambiato in =.
do
  read nome                   # Legge da $Nomefile, invece che dallo stdin.
  echo $nome
done <"$Nomefile"             # Redirige lo stdin nel file $Nomefile.
#    ^^^^^^^^^^^^

# Stessi risultati del ciclo "while" dell'esempio precedente.

exit 0

Esempio 19-8. Ciclo for rediretto

#!/bin/bash

if [ -z "$1" ]
then
  Nomefile=nomi.data           #  È il file predefinito, se non ne viene
                               #+ specificato alcuno.
else
  Nomefile=$1
fi

conta_righe=`wc $Nomefile | awk '{ print $1 }'`
#           Numero di righe del file indicato.
#
#  Elaborato e con diversi espedienti, ciò nonostante mostra che
#+ è possibile redirigere lo stdin in un ciclo "for" ...
#+ se si è abbastanza abili.
#
# Più conciso     conta_righe=$(wc -l < "$Nomefile")


for nome in `seq $conta_righe` #  Ricordo che "seq" genera una sequenza
                               #+ di numeri.
# while [ "$nome" != Smith ]   --  più complicato di un ciclo "while"  --
do
  read nome                    #  Legge da $Nomefile, invece che dallo stdin.
  echo $nome
  if [ "$nome" = Smith ]       #  Sono necessarie tutte queste istruzioni
                               #+ aggiuntive.
  then
      break
  fi
done <"$Nomefile"              #  Redirige lo stdin nel file $Nomefile.
#    ^^^^^^^^^^^^

exit 0

Il precedente esempio può essere modificato per redirigere anche l'output del ciclo.

Esempio 19-9. Ciclo for rediretto (rediretti sia lo stdin che lo stdout)

#!/bin/bash

if [ -z "$1" ]
then
  Nomefile=nomi.data             #  È il file predefinito, se non ne viene
                                 #+ specificato alcuno.
else
  Nomefile=$1
fi

Filereg=$Nomefile.nuovo          #  Nome del file in cui vengono salvati i
                                 #+ risultati.
NomeFinale=Jonah                 #  Nome per terminare la "lettura".

conta_righe=`wc $Nomefile | awk '{ print $1 }'` #  Numero di righe del file
                                                #+ indicato.

for nome in `seq $conta_righe`
do
  read nome
  echo "$nome"
  if [ "$nome" = "$NomeFinale" ]
  then
      break
  fi
done < "$Nomefile" > "$Filereg"  # Redirige lo stdin nel file $Nomefile,
#    ^^^^^^^^^^^^^^^^^^^^^^^^^^    e lo salva nel file di backup $Filereg.

exit 0

Esempio 19-10. Costrutto if/then rediretto

#!/bin/bash

if [ -z "$1" ]
then
  Nomefile=nomi.data    #  È il file predefinito, se non ne viene
                        #+ specificato alcuno.
else
  Nomefile=$1
fi

TRUE=1

if [ "$TRUE" ]          # vanno bene anche  if true   e   if :
then
 read nome
 echo $nome
fi <"$Nomefile"
#  ^^^^^^^^^^^^

#  Legge solo la prima riga del file.
#  Il costrutto "if/then" non possiede alcuna modalità di iterazione
#+ se non inserendolo in un ciclo.

exit 0

Esempio 19-11. File dati nomi.data usato negli esempi precedenti

Aristotile
Belisario
Capablanca
Eulero
Goethe
Hamurabi
Jonah
Laplace
Maroczy
Purcell
Schmidt
Semmelweiss
Smith
Turing
Venn
Wilson
Znosko-Borowski

#  Questo è il file dati per
#+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh".

Redirigere lo stdout di un blocco di codice ha l'effetto di salvare il suo output in un file. Vedi Esempio 3-2.

Gli here document rappresentano casi particolari di blocchi di codice rediretti. Stando così le cose, è possibile redirigere l'output di un here document nello stdin per un ciclo while.

# Esempio fornito da Albert Siersema
# Usato con il suo permesso (grazie!).
		
function creaOutput()
 # Naturalmente, potrebbe anche essere un comando esterno.
 # Qui viene mostrato come si possa usare una funzione allo stesso modo.
{
  ls -al *.jpg | awk '{print $5,$9}'
}
		
		
nr=0           #  Vogliamo che il ciclo while sia in grado di manipolare queste
dimensTotale=0 #+ variabili e visualizzare i cambiamenti al termine del ciclo.
		
while read dimensFile Nomefile ; do
  echo "$Nomefile è grande $dimensFile byte"
  let nr++
  dimensTotale=$((dimensTotale+dimensFile))   
  # O: "let dimensTotale+=dimensFile"
done<<EOF
$(creaOutput)
EOF
		
echo "$nr file per un totale di $totalSize byte"