19.2. Rediriger les blocs de code

Les blocs de code, comme les boucles while, until et for, voire même les blocs de test if/then peuvent aussi incorporer une redirection de stdin. Même une fonction peut utiliser cette forme de redirection (voir l'Exemple 23.11, « Vrai nom pour un utilisateur »). L'opérateur < à la fin du bloc de code accomplit ceci.

Exemple 19.5. Boucle while redirigée

#!/bin/bash
# redir2.sh

if [ -z "$1" ]
then
  Fichier=noms.donnees       # par défaut, si aucun fichier n'est spécifié.
else
  Fichier=$1
fi  
#+ Fichier=${1:-noms.donnees}
#  peut remplacer le test ci-dessus (substitution de paramètres).

compteur=0

echo

while [ "$nom" != Smith ]  # Pourquoi la variable $nom est-elle entre guillemets?
do
  read nom                 # Lit à partir de $Fichier, plutôt que de stdin.
  echo $nom
  let "compteur += 1"
done <"$Fichier"           # Redirige stdin vers le fichier $Fichier. 
#    ^^^^^^^^^^^^

echo; echo "$compteur noms lus"; echo

exit 0

#  Notez que dans certains vieux langages de scripts shells,
#+ la boucle redirigée pourrait tourner dans un sous-shell.
# Du coup, $compteur renverrait 0, la valeur initialisée en dehors de la boucle.
#  Bash et ksh évitent de lancer un sous-shell *autant que possible*,
#+ de façon à ce que ce script, par exemple, tourne correctement.
# Merci à Heiner Steven pour nous l'avoir indiqué.

# Néanmoins...
#  Bash *peut* quelque fois commencer un sous-shell dans une boucle "while"
#+ alimentée par un *tube*,
#+ à distinguer d'une boucle "while" *redirigée*.

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

#  Merci à Bruno de Oliveira Schneider pour avoir démontré ceci
#+ avec l'astuce de code ci-dessus.
#  Et merci à Brian Onn pour avoir corriger une erreur dans un commentaire.

Exemple 19.6. Autre forme de boucle while redirigée

#!/bin/bash

# Ceci est une forme alternative au script précédent.

#  Suggéré par Heiner Steven
#+ comme astuce dans ces situations où une boucle de redirection est lancée
#+ comme un sous-shell, et donc que les variables à l'intérieur de la boucle
#+ ne conservent pas leurs valeurs une fois la boucle terminée.


if [ -z "$1" ]
then
  Fichier=noms.donnees     # Par défaut, si aucun fichier spécifié.
else
  Fichier=$1
fi  


exec 3<&0                  # Sauve stdin sur le descripteur de fichier 3.
exec 0<"$Fichier"          # Redirige l'entrée standard.

compteur=0
echo


while [ "$nom" != Smith ]
do
  read nom               # Lit à partir du stdin redirigé ($Fichier).
  echo $nom
  let "compteur += 1"
done                      #  La boucle lit à partir du fichier $Fichier
                          #+ à cause de la ligne 20.

#  La version originale de ce script terminait la boucle "while" avec
#+      done <"$Filename" 
#  Exercice :
#  Pourquoi cela n'est-il pas nécessaire ?


exec 0<&3                 # Restaure l'ancien stdin.
exec 3<&-                 # Ferme le temporaire fd 3.

echo; echo "$compteur noms lus"; echo

exit 0

Exemple 19.7. Boucle until redirigée

#!/bin/bash
# Identique à l'exemple précédent, mais avec une boucle "until".

if [ -z "$1" ]
then
  Fichier=noms.donnees  # Par défaut, si aucun nom de fichier n'est spécifié.
else
  Fichier=$1
fi  

# while [ "$nom" != Smith ]
until [ "$nom" = Smith ]     # Modification de  !=  en =.
do
  read nom                   # Lit à partir de $Fichier, plutôt que de stdin.
  echo $nom
done <"$Fichier"             # Redirige stdin vers le fichier $Fichier. 
#    ^^^^^^^^^^^^

# Même résultats qu'avec la boucle "while" du précédent exemple.

exit 0

Exemple 19.8. Boucle for redirigée

#!/bin/bash

if [ -z "$1" ]
then
  Fichier=noms.donnees          # Par défaut, si aucun fichier n'est spécifié.
else
  Fichier=$1
fi  

compteur_lignes=`wc $Fichier | awk '{ print $1 }'`
#           Nombre de lignes du fichier cible.
#
#  Très peu naturel, néanmoins cela montre qu'il est possible de rediriger
#+ stdin à l'intérieur d'une boucle "for"...
#+ si vous êtes assez intelligent.
#
# Une autre façon plus concise est    compteur_lignes=$(wc -l < "$Fichier")


for nom in `seq $compteur_lignes`  # Rappelez-vous que "seq" affiche une séquence de nombres.
# while [ "$nom" != Smith ]   --   plus compliqué qu'une boucle "while" --
do
  read nom                    # Lit à partir de $Fichier, plutôt que de stdin.
  echo $nom
  if [ "$nom" = Smith ]       # A besoin de tout ce bagage supplémentaire ici.
  then
    break
  fi  
done <"$Fichier"              # Redirige stdin vers le fichier $Fichier. 
#    ^^^^^^^^^^^^

exit 0

Nous pouvons modifier le précédent exemple pour rediriger aussi la sortie de la boucle.

Exemple 19.9. Rediriger la boucle for (à la fois stdin et stdout)

#!/bin/bash

if [ -z "$1" ]
then
  Fichier=noms.donnees # Par défaut, si aucun fichier n'est spécifié.
else
  Fichier=$1
fi  

FichierSauvegarde=$Fichier.nouveau # Fichier où sauvegarder les résultats.
NomFinal=Jonah                     # Nom par lequel terminer la lecture.

nb_lignes=`wc $Fichier | awk '{ print $1 }'`  # Nombre de lignes du fichier cible.


for nom in `seq $nb_lignes`
do
  read nom
  echo "$nom"
  if [ "$nom" = "$NomFinal" ]
  then
    break
  fi  
done < "$Fichier" > "$FichierSauvegarde"  # Redirige stdin dans $Fichier,
#    ^^^^^^^^^^^^^^^^^^^^^^^^^^^       et sauvegarde dans le fichier.

exit 0

Exemple 19.10. Rediriger un test if/then

#!/bin/bash

if [ -z "$1" ]
then
  Fichier=noms.donnees   #  Valeur par défaut, si aucun nom de fichier n'est
                         #+ spécifié.
else
  Fichier=$1
fi  

VRAI=1

if [ "$VRAI" ]          # if true    et   if :   fonctionnent aussi.
then
 read nom
 echo $nom
fi <"$Fichier"
#  ^^^^^^^^^^^^

# Lit seulement la première ligne du fichier.
#  Un test "if/then" n'a aucun moyen de faire une itération sauf si il est
#+ intégré dans une boucle.

exit 0

Exemple 19.11. Fichier de données « nom.données » pour les exemples ci-dessus

Aristotle
Belisarius
Capablanca
Euler
Goethe
Hamurabi
Jonah
Laplace
Maroczy
Purcell
Schmidt
Semmelweiss
Smith
Turing
Venn
Wilson
Znosko-Borowski

#  This is a data file for
#+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh".

Rediriger stdout d'un bloc de code a le même effet que d'en sauver la sortie dans un fichier. Voir l'Exemple 3.2, « Sauver le résultat d'un bloc de code dans un fichier ».

Les documents en ligne sont un cas spécial pour la redirection de blocs de code.