web-dev-qa-db-fra.com

Substitution de processus et tuyau

Je me demandais comment comprendre ce qui suit:

Transférer la sortie standard d'une commande dans la sortie standard d'une autre est une technique puissante. Mais que faire si vous avez besoin de diriger la sortie standard de plusieurs commandes? C'est là qu'intervient la substitution de processus.

En d'autres termes, le processus de substitution peut-il faire tout ce que le tuyau peut faire?

Que peut faire la substitution, mais pas le tuyau?

89
Tim

Un bon moyen de faire la différence entre eux est de faire un peu d'expérimentation sur la ligne de commande. Malgré la similitude visuelle de l'utilisation du < caractère, il fait quelque chose de très différent d'une redirection ou d'un canal.

Utilisons la commande date pour les tests.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Ceci est un exemple inutile mais il montre que cat a accepté la sortie de date sur STDIN et l'a recrachée. Les mêmes résultats peuvent être obtenus par substitution de processus:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Mais ce qui vient de se passer dans les coulisses est différent. Au lieu de recevoir un flux STDIN, cat a en fait reçu le nom d'un fichier dont il avait besoin pour s'ouvrir et lire. Vous pouvez voir cette étape en utilisant echo au lieu de cat.

$ echo <(date)
/proc/self/fd/11

Lorsque cat a reçu le nom du fichier, il a lu le contenu du fichier pour nous. En revanche, l'écho vient de nous montrer le nom du fichier qu'il a été transmis. Cette différence devient plus évidente si vous ajoutez plus de substitutions:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Il est possible de combiner la substitution de processus (qui génère un fichier) et la redirection d'entrée (qui connecte un fichier à STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Il semble à peu près la même chose, mais cette fois, cat a reçu le flux STDIN au lieu d'un nom de fichier. Vous pouvez le voir en l'essayant avec écho:

$ echo < <(date)
<blank>

Comme echo ne lit pas STDIN et qu'aucun argument n'a été passé, nous n'obtenons rien.

Les tuyaux et les redirections d'entrée poussent le contenu sur le flux STDIN. La substitution de processus exécute les commandes, enregistre leur sortie dans un fichier temporaire spécial, puis transmet ce nom de fichier à la place de la commande. Quelle que soit la commande que vous utilisez, elle est traitée comme un nom de fichier. Notez que le fichier créé n'est pas un fichier normal mais un canal nommé qui est supprimé automatiquement une fois qu'il n'est plus nécessaire.

140
Caleb

Voici trois choses que vous pouvez faire avec la substitution de processus qui sont impossibles autrement.

Entrées de processus multiples

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Il n'y a tout simplement aucun moyen de le faire avec des tuyaux.

Préserver STDIN

Disons que vous disposez des éléments suivants:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

Et vous voulez l'exécuter directement. Ce qui suit échoue lamentablement. Bash utilise déjà STDIN pour lire le script, donc toute autre entrée est impossible.

curl -o - http://example.com/script.sh | bash 

Mais cette façon fonctionne parfaitement.

bash <(curl -o - http://example.com/script.sh)

Substitution de processus sortant

Notez également que la substitution de processus fonctionne également dans l'autre sens. Vous pouvez donc faire quelque chose comme ceci:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

C'est un peu un exemple compliqué, mais il envoie stdout à /dev/null, tout en redirigeant stderr vers un script sed pour extraire les noms des fichiers pour lesquels une erreur "Autorisation refusée" a été affichée, puis envoie CES résultats vers un fichier.

Notez que la première commande et la redirection stdout sont entre parenthèses (subshell) afin que seul le résultat de cette commande soit envoyé à /dev/null et ça ne dérange pas avec le reste de la ligne.

26
tylerl

Je suppose que vous parlez de bash ou d'un autre shell avancé, car le shell posix n'a pas substitution de processus.

bash rapports de page de manuel:

Substitution de processus
La substitution de processus est prise en charge sur les systèmes qui prennent en charge les canaux nommés (FIFO) ou la méthode/dev/fd pour nommer les fichiers ouverts. Il prend la forme de <(liste) ou> (liste). La liste des processus est exécutée avec son entrée ou sa sortie connectée à un FIFO ou à un fichier dans/dev/fd. Le nom de ce fichier est transmis comme argument à la commande en cours à la suite de l'extension. Si le formulaire> (liste) est utilisé, l'écriture dans le fichier fournira une entrée pour la liste. Si le formulaire <(liste) est utilisé, le fichier transmis comme argument doit être lu pour obtenir la sortie de la liste.

Lorsqu'elle est disponible, la substitution de processus est effectuée simultanément avec l'expansion des paramètres et des variables, la substitution des commandes et l'expansion arithmétique.

En d'autres termes, et d'un point de vue pratique, vous pouvez utiliser une expression comme la suivante

<(commands)

comme nom de fichier pour d'autres commandes nécessitant un fichier comme paramètre. Ou vous pouvez utiliser la redirection pour un tel fichier:

while read line; do something; done < <(commands)

Pour revenir à votre question, il me semble que la substitution de processus et les tuyaux n'ont pas grand-chose en commun.

Si vous souhaitez diriger en séquence la sortie de plusieurs commandes, vous pouvez utiliser l'une des formes suivantes:

(command1; command2) | command3
{ command1; command2; } | command3

mais vous pouvez également utiliser la redirection sur la substitution de processus

command3 < <(command1; command2)

enfin, si command3 accepte un paramètre de fichier (en remplacement de stdin)

command3 <(command1; command2)
26
enzotib

Si une commande prend une liste de fichiers comme arguments et traite ces fichiers en entrée (ou en sortie, mais pas couramment), chacun de ces fichiers peut être un canal nommé ou un pseudo-fichier/dev/fd fourni de manière transparente par la substitution de processus:

$ sort -m <(command1) <(command2) <(command3)

Cela "dirigera" la sortie des trois commandes à trier, car le tri peut prendre une liste de fichiers d'entrée sur la ligne de commande.

10
camh

Il convient de noter que la substitution de processus n'est pas limitée à la forme <(command), qui utilise la sortie de command comme fichier. Il peut être sous la forme >(command) qui alimente également un fichier en command. Ceci est également mentionné dans la citation du manuel bash dans la réponse de @ enzotib.

Pour l'exemple date | cat Ci-dessus, une commande qui utilise la substitution de processus de la forme >(command) pour obtenir le même effet serait,

date > >(cat)

Notez que le > Avant >(cat) est nécessaire. Cela peut encore être clairement illustré par echo comme dans la réponse de @ Caleb.

$ echo >(cat)
/dev/fd/63

Donc, sans le supplément >, date >(cat) serait la même chose que date /dev/fd/63 Qui imprimera un message à stderr.

Supposons que vous ayez un programme qui ne prend que les noms de fichiers comme paramètres et ne traite pas stdin ou stdout. J'utiliserai le script simplifié psub.sh Pour illustrer cela. Le contenu de psub.sh Est

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Fondamentalement, il teste que ses deux arguments sont des fichiers (pas nécessairement des fichiers normaux) et si c'est le cas, écrivez le premier champ de chaque ligne de "$1" Dans "$2" En utilisant awk. Ensuite, une commande qui combine tout ce qui a été mentionné jusqu'ici est,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Cela imprimera

a
b
c

et est équivalent à

printf "a a\nc c\nb b" | awk '{print $1}' | sort

mais ce qui suit ne fonctionnera pas, et nous devons utiliser la substitution de processus ici,

printf "a a\nc c\nb b" | ./psub.sh | sort

ou sa forme équivalente

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Si ./psub.sh Lit également stdin en plus de ce qui est mentionné ci-dessus, alors un tel formulaire équivalent n'existe pas, et dans ce cas, nous ne pouvons rien utiliser à la place de la substitution de processus (bien sûr, vous pouvez utilisez également un fichier nommé pipe ou temp, mais c'est une autre histoire).

3
Weijun Zhou