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
>(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]
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
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 $?
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.