Processus de substitution et pipe

86

Je me demandais comment comprendre ce qui suit:

Piping du stdout d'une commande dans le stdin d'un autre est une technique puissante. Mais que se passe-t-il si vous devez canaliser la sortie standard de plusieurs commandes? C'est ici qu'intervient la substitution de processus.

En d'autres termes, la substitution de processus peut-elle faire tout ce que le canal peut faire?

Qu'est-ce que la substitution de processus peut faire, mais pas la pipe?

Tim
la source

Réponses:

134

Un bon moyen de faire la différence est de faire quelques essais sur la ligne de commande. Malgré la similarité visuelle dans l'utilisation du <personnage, il fait quelque chose de très différent d'une redirection ou d'un pipe.

Utilisons la datecommande pour tester.

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

Ceci est un exemple inutile mais il montre que catla sortie de dateSTDIN a été acceptée et renvoyée. Les mêmes résultats peuvent être obtenus par substitution de processus:

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

Cependant, ce qui vient de se passer dans les coulisses était différent. Au lieu de recevoir un flux STDIN, catle nom du fichier qu’il devait ouvrir et lire a été transmis. Vous pouvez voir cette étape en utilisant à la echoplace de cat.

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

Lorsque chat reçoit le nom du fichier, il lit le contenu du fichier pour nous. D'autre part, echo nous a juste montré 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 des entrées (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, le chat a été passé flux STDIN au lieu d'un nom de fichier. Vous pouvez le voir en l'essayant avec echo:

$ echo < <(date)
<blank>

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

Les pipes et les redirections d'entrée transfèrent 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 la traite comme un nom de fichier. Notez que le fichier créé n'est pas un fichier normal, mais un canal nommé qui est automatiquement supprimé dès qu'il n'est plus nécessaire.

Caleb
la source
Si j'ai bien compris, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 indique que la substitution de processus crée des fichiers temporaires, et non des canaux nommés. Autant que je sache, nommés ne créez pas de fichiers temporaires. Écrire dans le tuyau n'implique jamais d'écrire sur le disque: stackoverflow.com/a/6977599/788700
Adobe
Je sais que cette réponse est légitime car elle utilise le mot grok : D
aqn
2
@Adobe vous pouvez confirmer si la substitution de processus de fichier temporaire produit est un tube nommé avec: [[ -p <(date) ]] && echo true. Cela se produit truequand je le lance avec bash 4.4 ou 3.2.
De Novo
24

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

bash rapports de page de manuel:

Substitution de
processus La substitution de processus est prise en charge sur les systèmes prenant en charge les canaux nommés (FIFO) ou la méthode / dev / fd de nommer des fichiers ouverts. Il prend la forme de <(liste) ou> (liste). La liste de processus est exécutée avec son entrée ou sa sortie connectée à une FIFO ou à un fichier dans / dev / fd. Le nom de ce fichier est passé en tant qu'argument à la commande en cours à la suite de l'expansion. 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 en tant qu'argument doit être lu pour obtenir le résultat de la liste.

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

En d'autres termes, et d'un point de vue pratique, vous pouvez utiliser une expression comme celle-ci

<(commands)

en tant que nom de fichier pour d'autres commandes nécessitant un fichier en tant que 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 séquentiellement la sortie de plusieurs commandes, vous pouvez utiliser l’un des formulaires suivants:

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

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

command3 < <(command1; command2)

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

command3 <(command1; command2)
enzotib
la source
alors <() et <<() ont le même effet, non?
solfish
@solfish: not exacllty: le firse peut être utilisé partout où un nom de fichier est attendu, le second est une redirection d'entrée pour ce nom de fichier
enzotib
23

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

Entrées de processus multiples

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

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

Conserver STDIN

Supposons que vous ayez:

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, une autre entrée est donc 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 dans l'autre sens également. Donc, vous pouvez faire quelque chose comme ça:

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

C’est un exemple un peu compliqué, mais il envoie stdout à /dev/null, pendant que nous transmettons stderr à un script sed afin d’extraire les noms des fichiers pour lesquels une erreur "Autorisation refusée" était affichée, puis envoie CES résultats à un fichier.

Notez que la première commande et la redirection stdout sont entre parenthèses ( sous-shell ), de sorte que seul le résultat de la commande THAT est envoyé à /dev/nullet que cela ne gâche pas le reste de la ligne.

tylerl
la source
Il convient de noter que , dans l' diffexemple , vous voudrez peut - être se soucier de cas où le cdpeut échouer: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk
"while piping stderr": n'est-il pas vrai que ce n'est pas une tuyauterie, mais un fichier fifo?
Gauthier
@Gauthier non; la commande est substituée non pas par un fifo mais par une référence au descripteur de fichier. Ainsi, "echo <(echo)" devrait donner quelque chose comme "/ dev / fd / 63", qui est un périphérique à caractères spéciaux qui lit ou écrit à partir du numéro FD 63.
tylerl
10

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

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

Cela "acheminera" la sortie des trois commandes à trier, car trier peut prendre une liste de fichiers en entrée sur la ligne de commande.

camh
la source
1
IIRC la syntaxe <(commande) est une fonctionnalité uniquement bash.
Philomath
@Philomath: C'est aussi en ZSH.
Caleb
Eh bien, ZSH a tout ... (ou du moins tente de le faire).
Philomath
@Philomath: Comment la substitution de processus est-elle implémentée dans d'autres shells?
camh le
4
@Philomath <(), à l'instar de nombreuses fonctionnalités avancées du shell, était à l'origine une fonctionnalité ksh et a été adopté par bash et zsh. psubest spécifiquement une fonctionnalité de poisson, rien à voir avec POSIX.
Gilles
3

Il convient de noter que la substitution de processus ne se limite pas au formulaire <(command), qui utilise la sortie de commandsous forme de fichier. Cela peut aussi être sous la forme >(command)qui alimente un fichier comme entrée command. Ceci est également mentionné dans la citation de bash manual dans la réponse de @ enzotib.

Pour l' date | catexemple ci-dessus, une commande qui utilise la substitution de processus du formulaire >(command)pour obtenir le même effet serait,

date > >(cat)

Notez que l' >avant >(cat)est nécessaire. Ceci peut encore être clairement illustré par echola réponse de @ Caleb.

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

Donc, sans l’extra >, ce date >(cat)serait la même chose que d’ date /dev/fd/63imprimer un message à stderr.

Supposons que vous ayez un programme qui prend uniquement les noms de fichiers en tant que paramètres et ne traite pas stdinou stdout. Je vais utiliser le script trop simpliste psub.shpour illustrer cela. Le contenu de psub.shest

#!/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"à l' "$2"aide de awk. Ensuite, une commande qui combine tout ce qui est mentionné jusqu'à présent est,

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

Cela va imprimer

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.shlit aussi d' stdinailleurs ce qui est mentionné ci - dessus, une telle forme équivalente n'existe pas, et dans ce cas il y a rien que nous pouvons utiliser à la place de la substitution de processus (bien sûr vous pouvez également utiliser un fichier de tuyau ou nommé temp, mais c'est une autre récit).

Weijun Zhou
la source