Comprendre les commandes canalisées sous Unix / Linux

16

J'ai deux programmes simples: Aet B. As'exécuterait en premier, puis Bobtient la «stdout» de Aet l'utilise comme son «stdin». Supposons que j'utilise un système d'exploitation GNU / Linux et la manière la plus simple de le faire serait:

./A | ./B

Si je devais décrire cette commande, je dirais que c'est une commande qui prend l'entrée (c'est-à-dire la lecture) d'un producteur ( A) et écrit sur un consommateur ( B). Est-ce une description correcte? Suis-je en train de manquer quelque chose?

nihulus
la source
Connexes: dans quel ordre les commandes canalisées s'exécutent-elles?
G-Man dit `` Réintègre Monica '' le
Ce n'est pas une commande, c'est un objet kenerl créé par bash process, qui est utilisé comme stdout du processus A et stdin comme B. Deux processus sont démarrés presque en même temps.
炸鱼 薯条 德里克
1
@ 炸鱼 Vous avez raison - car le pipeline du noyau est un objet du système de fichiers pipefs, mais en ce qui concerne le shell lui-même - techniquement, c'est une commande de pipeline
Sergiy Kolodyazhnyy

Réponses:

26

La seule chose à votre question qui se démarque comme erronée est que vous dites

A serait exécuté en premier, puis B obtient la sortie standard de A

En fait, les deux programmes seraient lancés à peu près en même temps. S'il n'y a pas d'entrée pour Bquand il essaie de lire, il se bloquera jusqu'à ce qu'il y ait une entrée à lire. De même, s'il n'y a personne qui lit la sortie A, ses écritures seront bloquées jusqu'à ce que sa sortie soit lue (une partie sera tamponnée par le tube).

La seule chose qui synchronise les processus qui participent à un pipeline est les E / S, c'est-à-dire la lecture et l'écriture à travers le tuyau. Si aucune écriture ou lecture ne se produit, les deux processus s'exécuteront de manière totalement indépendante l'un de l'autre. Si l'un ignore la lecture ou l'écriture de l'autre, le processus ignoré se bloquera et finira par être tué par un SIGPIPEsignal (s'il écrit) ou obtiendra une condition de fin de fichier sur son flux d'entrée standard (si la lecture) lorsque l'autre processus se termine .

La façon idiomatique de décrire A | Best qu'il s'agit d'un pipeline contenant deux programmes. La sortie produite sur la sortie standard du premier programme est disponible pour être lue sur l'entrée standard par le second ("[la sortie de] Aest canalisée dans [l'entrée de] B"). La coque fait la plomberie requise pour permettre que cela se produise.

Si vous voulez utiliser les mots «consommateur» et «producteur», je suppose que ça va aussi.

Le fait qu'il s'agisse de programmes écrits en C n'est pas pertinent. Le fait qu'il s'agisse de Linux, macOS, OpenBSD ou AIX n'est pas pertinent.

Kusalananda
la source
2
L'écriture dans un fichier temporaire a été utilisée sous DOS, car elle ne supportait pas plusieurs processus.
CSM
2
@AlexVong Notez cependant que votre exemple avec un fichier temporaire n'est pas exactement équivalent. Un programme peut choisir de rechercher le contenu d'un fichier, mais les données provenant d'un canal ne peuvent pas être recherchées. Un meilleur examen serait d'utiliser mkfifopour créer un canal nommé, puis démarrez B en arrière-plan en lisant le canal, puis A en y écrivant. Il s'agit cependant de choix, car l' effet serait le même.
Kusalananda
2
@AlexVong Les simplifications apportées dans cet article le séparent des pipelines réels; l'exécution parallèle est vraiment sémantique, pas une optimisation. C'est une explication mensongère raisonnable aux enfants de l'évaluation ou de la composition monadique pour quelqu'un qui a vu des pipelines d'obus, mais ce n'est pas valable dans l'autre sens. La version fifo de Kusalananda est plus proche, mais les parties de propagation d'erreur du modèle sont vraiment importantes et non reproductibles. (tout ce que je dis en tant que quelqu'un qui est très sur le train "les pipelines shell ne sont que des compositions de fonctions")
Michael Homer
6
@AlexVong Non, c'est complètement hors piste. Cela ne peut même pas expliquer quelque chose de simple comme yes | sed 10q
Oncle Billy
1
@UncleBilly Je suis d'accord avec votre exemple. Cela montre que l'exécution parallèle est vraiment requise également notée par Michael. Sinon, nous obtiendrons la non-résiliation.
Alex Vong
2

Le terme habituellement utilisé dans la documentation est "pipeline", qui consiste en une ou plusieurs commandes, voir la définition POSIX Donc, techniquement parlant, c'est deux commandes que vous avez là, deux sous-processus pour le shell (soit fork()+exec()des commandes externes éditées ou des sous-shell)

Quant à la partie producteur-consommateur , le pipeline peut être décrit par ce schéma, car:

  • Le producteur et le consommateur partagent un tampon de taille fixe, et au moins sur Linux et MacOS X, il existe une taille fixe pour le tampon du pipeline
  • Le producteur et le consommateur sont faiblement couplés, les commandes dans le pipeline ne connaissent pas l'existence de l'autre (à moins qu'elles ne vérifient activement le /proc/<pid>/fdrépertoire).
  • Les producteurs écrivent stdoutet les consommateurs lisent stdincomme s'il s'agissait d'une seule commande en cours d'exécution, c'est-à-dire qu'ils peuvent exister les uns sans les autres .

La différence que je vois ici est que, contrairement à Producer-Consumer dans d'autres langues, les commandes shell utilisent la mise en mémoire tampon et écrivent stdout une fois que la mémoire tampon est remplie, mais il n'y a aucune mention que Producer-Consumer doit suivre cette règle - n'attendez que lorsque la file d'attente est remplie ou jetée les données (ce qui est autre chose que le pipeline ne fait pas).

Sergiy Kolodyazhnyy
la source