23. Substitution de processus

Piping the stdout of a command into the stdin of another is a powerful technique. But, what if you need to pipe the stdout of multiple commands? This is where process substitution comes in.

La substitution de processus envoie la sortie d'un ou de plusieurs processus vers l'entrée standard d'un autre processus.

Patron

commande entre parenthèses

>(liste_de_commandes)

<(liste_de_commandes)

La substitution de processus utilise les fichiers /dev/fd/<n>; pour envoyer le résultat des processus entre parenthèses vers un autre processus. [104]

[Attention]

Attention

Il n'y a pas d'espace entre le « < » ou « > » et les parenthèses. Ici, une espace génèrerait un message d'erreur.

bash$ echo >(true)
/dev/fd/63

bash$ echo <(true)
/dev/fd/63

bash$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62

bash$ wc <(cat /usr/share/dict/linux.words)
 483523  483523 4992010 /dev/fd/63

bash$ grep script /usr/share/dict/linux.words | wc
    262     262    3601

bash$ wc <(grep script /usr/share/dict/linux.words)
    262     262    3601 /dev/fd/63
              
[Note]

Note

Bash crée un tube avec deux descripteurs de fichiers, --fIn et fOut--. Le stdin (entrée standard) de true se connecte à fOut (la sortie standard) (dup2(fOut, 0)), puis Bash passe un /dev/fd/fIn comme argument à la commande echo. Sur les systèmes sans fichier /dev/fd/<n>, Bash peut utiliser des fichiers temporaires (merci à S.C.).

La substitution de processus peut comparer la sortie de deux commandes différentes, voire même la sortie dûe à différentes options de la même commande.

bash$ 
         comm <(ls -l) <(ls -al)
total 12
-rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
-rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
-rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh
total 20
drwxrwxrwx    2 bozo bozo     4096 Mar 10 18:10 .
drwx------   72 bozo bozo     4096 Mar 10 17:58 ..
-rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
-rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
-rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh

La substitution de processus permet de comparer le contenu de deux répertoires -- pour voir quels noms de fichiers se trouvent dans l'un dans se trouver dans l'autre.

diff <(ls
                $premier_repertoire) <(ls
                $deuxieme_repertoire)

Quelques autres utilisations de la substitution de processus :

read -a list < <( od -Ad -w24 -t u2 /dev/urandom )
#  Lit une liste de nombres aléatoires à partir de /dev/urandom,
#+ les traite avec « od »
#+ et les envoit dans l'entrée standard de « read »...

#  Provient du script exemple "insertion-sort.bash".
#  Merci à JuanJo Ciarlante.

PORT=6881   # bittorrent

# Scanne le port pour vérifier qu'il ne s'y produit rien de malfaisant.
netcat -l $PORT | tee>(md5sum >mesdonnees-orig.md5) |
gzip | tee>(md5sum - | sed 's/-$/mesdonnees.lz2/'>mesdonnees-gz.md5)>mesdonnees.gz

#  Vérifie la décompression:
gzip -d<mesdonnees.gz | md5sum -c mesdonnees-orig.md5)
#  La somme MD5 de l'original vérifie stdin et détecte les problèmes de
#+ compression.

#  Cet exemple nous a été envoyé par Bill Davidsen (avec quelques
#+ légères modifications par l'auteur du Guide ABS).
cat <(ls -l)
# Même chose que   ls -l | cat

sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin)
#  Liste tous les fichiers des trois principaux répertoires "bin" et les trie
#+ par nom de fichier.
# Notez les trois commandes distinctes (Comptez les <) vont "nourrir" 'sort'.


diff <(command1) <(command2)    #  Fournit les différences entre les
                                #+ sorties des commandes.

tar cf >(bzip2 -c > file.tar.bz2) $nom_repertoire
# Appelle "tar cf /dev/fd/?? $nom_repertoire" et "bzip2 -c > fichier.tar.bz2"
#
# À cause de la fonctionnalité système /dev/fd/<n>,
# le tube entre les deux commandes n'a pas besoin d'être nommé.
#
# Ceci peut être émulé.
#
bzip2 -c < pipe > fichier.tar.bz2&
tar cf pipe $nom_repertoire
rm pipe
#        ou
exec 3>&1
tar cf /dev/fd/4 $nom_repertoire 4>&1 >&3 3>&- | bzip2 -c > fichier.tar.bz2 3>-
exec 3>&-


# Merci à Stéphane Chazelas

Voici une méthode de contournement du problème de l'echo enchaîné à une boucle while-read, exécuté dans un sous-shell.

Exemple 23.1. Redirection de bloc de code dans une unique instance

#!/bin/bash
# wr-ps.bash: while-read loop with process substitution.

# This example contributed by Tomas Pospisek.
# (Heavily edited by the ABS Guide author.)

echo

echo "random input" | while read i
do
  global=3D": Not available outside the loop."
  # ... because it runs in a subshell.
done

echo "\$global (from outside the subprocess) = $global"
# $global (from outside the subprocess) =

echo; echo "--"; echo

while read i
do
  echo $i
  global=3D": Available outside the loop."
  # ... because it does *not* run in a subshell.
done < <( echo "random input" )
#    ^ ^

echo "\$global (using process substitution) = $global"
# Random input
# $global (using process substitution) = 3D: Available outside the loop.


echo; echo "##########"; echo



# And likewise . . .

declare -a inloop
index=0
cat $0 | while read line
do
  inloop[$index]="$line"
  ((index++))
  # It runs in a subshell, so ...
done
echo "OUTPUT = "
echo ${inloop[*]}           # ... nothing echoes.


echo; echo "--"; echo


declare -a outloop
index=0
while read line
do
  outloop[$index]="$line"
  ((index++))
  # It does *not* run in a subshell, so ...
done < <( cat $0 )
echo "OUTPUT = "
echo ${outloop[*]}          # ... the entire script echoes.

exit $?

Voici un exemple proche.

Exemple 23.2. Redirection de la sortie d'une substitution de processus vers une boucle.

#!/bin/bash
# psub.bash

# Tel qu'inspiré par Diego Molina (merci !).
# Traduction : Jean-Philippe Guérard

declare -a tableau0
while read
do
  tableau0[${#tableau0[@]}]="$REPLY"
done < <( sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}' )
#  Utilise la substitution de processus pour définir « $REPLY », la
#+ variable par défaut renvoyée par « read », puis la copie dans un
#+ tableau.

echo "${tableau0[@]}"

exit $?

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

bash psub.bash

#!/bin/CRASH-BANG! done #!/bin/CRASH-BANG!

Un lecteur nous a envoyé cet intéressant exemple de substitution de processus.

# Fragment de script provenant d'une distribution Suse :

# --------------------------------------------------------------#
while read des what mask iface; do
# Quelques commandes ...
done < <(route -n)
#    ^ ^  Le premier < est la redirection,
#         le second correspond à la substitution du processus.

# Pour le tester, faisons lui faire quelque chose
while read  des what mask iface; do
  echo $des $what $mask $iface
done < <(route -n)

# Sortie:
# Table de routage IP du noyau
# Destination Gateway Genmask Flags Metric Ref Use Iface
# 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
# --------------------------------------------------------------#

# Comme  Stéphane Chazelas le souligne, voici un équivalent plus aisément compréhensible :
route -n |
  while read des what mask iface; do   #  Les variables sont affectées par la
                                       #+ sortie du tube.
    echo $des $what $mask $iface
  done  #  Ceci engendre la même sortie que ci-dessus.
        #  Néanmoins, comme le précise Ulrich Gayer...
        #+ cet équivalent simplifié utilise un sous-shell pour la boucle while
        #+ et donc les variables disparaissent quand l'envoi via le tube se
        #+ termine.

# --------------------------------------------------------------#

#  Néanmoins, Filip Moritz indique qu'il existe une différence subtile
#+ entre les deux exemples ci-dessus, comme nous le montre la suite.

(
route -n | while read x; do ((y++)); done
echo $y # $y n'est toujours pas initialisé

while read x; do ((y++)); done < <(route -n)
echo $y # $y a le nombre de lignes en sortie de route -n
)

# Plus généralement
(
: | x=x
# semble lancer un sous-shell comme
: | ( x=x )
# alors que
x=x < <(:)
# ne le fait pas
)

# C'est utile pour analyser csv ou un fichier de ce genre.
# En effet, c'est ce que fait le fragement de code SuSE original.


[104] Ceci a le même effet qu'un tube nommé (fichier temporaire), et, en fait, les tubes nommés étaient autrefois utilisés dans les substitutions de processus.