Dans quel ordre les commandes piped sont-elles exécutées?

89

Je n'ai jamais vraiment réfléchi à la manière dont le shell exécute les commandes redirigées. On m'a toujours dit que "la sortie standard d'un programme entrait dans le répertoire standard d'un autre", comme moyen de penser aux pipes. Alors naturellement, je pensais que dans le cas de, A | B, A s'exécute en premier, puis B obtient la sortie standard de A et utilise la sortie standard de A comme entrée.

Mais j'ai remarqué que lorsque les gens recherchent un processus particulier dans ps, ils ajoutent grep -v "grep" à la fin de la commande pour s'assurer que grep n'apparaît pas dans la sortie finale. Cela signifie que dans la commande ps aux | grep "bash" | grep -v "grep", ce qui signifie que ps savait que grep était en cours d'exécution et qu'il se trouvait donc dans la sortie de ps. Mais si ps termine de s'exécuter avant que sa sortie ne soit transmise à grep, comment a-t-il su que grep était en cours d'exécution?

flamingtoast@FTOAST-UBUNTU: ~$ ps | grep ".*"
PID TTY          TIME CMD
3773 pts/0    00:00:00 bash
3784 pts/0    00:00:00 ps
3785 pts/0    00:00:00 grep
action_potato
la source
pourquoi ne pas accepter une réponse?
törzsmókus

Réponses:

64

Les commandes canalisées sont exécutées simultanément. Lorsque vous exécutez ps | grep …, c'est la chance du tirage (ou une question de détails sur le fonctionnement de la coque combinée avec un planificateur affiné au fond des entrailles du noyau) quant à savoir si psou grepcommence en premier, et dans tous les cas, ils continuent exécuter simultanément.

Ceci est très couramment utilisé pour permettre au second programme de traiter les données telles qu'elles sortent du premier programme, avant que le premier programme ait terminé son opération. Par exemple

grep pattern very-large-file | tr a-z A-Z

commence à afficher les lignes correspondantes en majuscule avant même que greple fichier volumineux ait été parcouru.

grep pattern very-large-file | head -n 1

affiche la première ligne correspondante et peut arrêter le traitement bien avant la grepfin de la lecture de son fichier d'entrée.

Si vous lisez quelque part que les programmes distribués s'exécutent en séquence, fuyez ce document. Les programmes distribués en parallèle s’exécutent simultanément.

Gilles
la source
7
Et ce qui est bien dans cet exemple, c’est que lorsque head obtient la ligne dont il a besoin, il se termine et lorsque grep le remarque, il se termine également sans faire beaucoup de travail supplémentaire pour rien.
Joe
Je suppose qu'il existe une sorte de tampon IO concernant le tuyau ... Comment savoir si sa taille est en octets? Qu'est-ce que je veux lire pour en savoir plus? :)
n611x007
3
@naxa Il y a deux tampons, en fait. Il y a le tampon stdio à l'intérieur du grepprogramme et un tampon géré par le noyau dans le tube lui-même. Pour ces derniers, voir Quelle est la taille du tampon de tuyau?
Gilles
49

L'ordre dans lequel les commandes sont exécutées importe peu et n'est pas garanti. Laissant de côté les détails obscurs de pipe(), fork(), dup()et execve(), la coquille crée d' abord le tuyau, le conduit pour les données qui coulera entre les processus, et crée ensuite les processus avec les extrémités du tube relié à eux. Le premier processus exécuté peut bloquer l'attente du deuxième processus ou attendre que le second commence à lire les données du canal. Ces attentes peuvent être arbitrairement longues et sans importance. Quel que soit l’ordre dans lequel les processus sont exécutés, les données sont finalement transférées et tout fonctionne.

Kyle Jones
la source
5
Bonne réponse, mais le PO semble penser que les processus se déroulent de manière séquentielle. Vous pouvez indiquer ici plus clairement que les processus sont exécutés simultanément et que le tuyau est comme un tuyau entre des seaux, dans lequel l’eau coule à peu près au même moment.
Keith
Merci pour la clarification. Les sources que j'ai lues donnaient l'impression que les programmes en pipeline fonctionnaient séquentiellement plutôt que simultanément.
action_potato
Pour voir l'expérience des processus démarrant de manière indéterminée, essayez de l'exécuter 1000 fois: echo -na> & 2 | echo b> & 2
Ole Tange
28

Au risque de frapper un cheval mort, l’idée fausse semble être que

    A | B

est équivalent à

    A > fichier_temporaire 
    B < fichier_temporaire 
    rm fichier_temporaire

Mais, à l'époque de la création d'Unix et de l'entrée des dinosaures à l'école chez les enfants, les disques étaient très petits et il était courant qu'une commande plutôt anodine consomme tout l'espace disponible dans un système de fichiers. Si cela Bressemblait à quelque chose , la sortie finale du pipeline pourrait être beaucoup plus petite que ce fichier intermédiaire. Par conséquent, le tuyau a été développé, non pas comme un raccourci pour la « exécuter un premier, puis exécutez B avec l' entrée de A la sortie de » modèle, mais comme un moyen pour exécuter en même temps et d' éliminer la nécessité de stocker le fichier intermédiaire sur le disque.grep some_very_obscure_stringBA

Scott
la source
2
Cela répond pourquoi et obtient donc mon vote.
Petite forêt antique de Kami le
1

Typiquement, vous exécutez ceci sous bash. processus fonctionnant et démarrant simultanément, mais exécutés par le shell en parallèle. Comment est-ce possible?

  1. si ce n'est pas la dernière commande dans le tube, créez un tube sans nom avec une paire de sockets
  2. fourchette
  3. in child réaffecter stdin / stdout à des sockets si nécessaire (pour le premier processus dans le canal, stdin n'est pas réaffecté, il en va de même pour le dernier processus et sa stdout)
  4. dans enfant, EXEC a spécifié la commande avec des arguments qui balayent le code shell original, tout en laissant tous les sockets ouverts. l'ID de processus enfant ne sera pas modifié car il s'agit du même processus enfant
  5. en même temps que child mais parallèlement sous shell principal, passez à l’étape 1.

le système ne garantit pas la rapidité d'exécution de exec et le démarrage de la commande spécifiée. il est indépendant du shell, mais du système. Ceci est dû au fait:

ps auxww| grep ps | cat

une fois montrer grepet / ou la pscommande, et maintenant maintenant. Cela dépend à quelle vitesse le noyau démarre réellement les processus utilisant la fonction exec du système.

Znik
la source
1
L' exécution simultanée signifie que deux processus ou plus s'exécutent dans le même laps de temps, généralement avec une sorte de dépendance entre eux. L' exécution parallèle signifie que deux processus ou plus s'exécutent simultanément (par exemple, sur des cœurs de CPU distincts au même moment). Le parallélisme n'est pas pertinent pour la question, pas plus que "la rapidité" exec()n'est exécutée, mais comment les exec()appels et l'exécution des programmes d'un canal sont entrelacés .
Thomas Nyman