20.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 24.11, « Vrai nom pour un utilisateur »). L'opérateur < à la fin du bloc de code accomplit ceci.

Exemple 20.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 20.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 20.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 20.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 20.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 20.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 20.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, « Enregistrer la sortie d'un bloc de code dans un fichier ».

Les documents intégrés sont un cas spécial pour la redirection de blocs de code. Dans ce cas, il devrait être possible d'alimenter le stdin d'une boucle while avec la sortie d'un document en ligne.

# Exemple de Albert Siersema
# Utilisé avec sa permission (merci !).

function sortie()
 # Pourrait aussi être une commande externe, bien sûr.
 # Ici, nous démontrons que vous pouvez utiliser aussi une fonction.
{
  ls -al *.jpg | awk '{print $5,$9}'
}


nr=0          #  Nous voulons que la boucle while puisse les manipuler et
totalSize=0   #+ nous voulons être capable de voir les changements
              #+ après la fin de 'while'.

while read tailleFichier nomFichier ; do
  echo "$nomFichier fait $tailleFichier octets"
  let nr++
  tailleTotale=$((tailleTotale+$tailleFichier))   # Or: "let tailleTotale+=tailleFichier"
done&lt;&lt;EOF
$(sortie)
EOF

echo "$nr fichiers totalisant $tailleTotale octets"