Pipes, comment les données circulent-elles dans un pipeline?

22

Je ne comprends pas comment les données circulent dans le pipeline et j'espère que quelqu'un pourrait clarifier ce qui se passe là-bas.

Je pensais qu'un pipeline de commandes traite les fichiers (texte, tableaux de chaînes) ligne par ligne. (Si chaque commande elle-même fonctionne ligne par ligne.) Chaque ligne de texte passe par le pipeline, les commandes n'attendent pas que la précédente termine le traitement de l'entrée entière.

Mais il semble que ce ne soit pas le cas.

Voici un exemple de test. Il y a quelques lignes de texte. Je les mets en majuscule et répète deux fois chaque ligne. Je le fais avec cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Pour suivre le processus, nous pouvons l'exécuter "de manière interactive" - ​​sautez le nom du fichier d'entrée dans cat. Chaque partie du pipeline fonctionne ligne par ligne:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Mais le pipeline complet attend que je termine l'entrée avec EOFet n'imprime alors que le résultat:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

Est-ce censé en être ainsi? Pourquoi n'est-ce pas ligne par ligne?

xealits
la source
Ce n'est pas la pipe, c'est la catmise en mémoire tampon jusqu'à la fermeture de stdin.
goldilocks
mais tret sedne traite les lignes d' catavant la fermeture de stdin
xealits
Les valeurs par défaut utilisées par stdio (que je crois que tous les programmes mentionnés utilisent) sont que stderr est sans tampon, et stdout est mis en mémoire tampon de ligne lors de l'écriture sur un terminal et entièrement mis en mémoire tampon dans le cas contraire (par exemple s'il écrit dans un fichier ou un canal) . Certaines commandes ont des drapeaux qui peuvent modifier la mise en mémoire tampon de la sortie standard, mais il semble que tr ne le soit pas.
kasperd

Réponses:

36

Il existe une règle générale de mise en mémoire tampon suivie de la bibliothèque d'E / S standard C ( stdio) que la plupart des programmes Unix utilisent. Si la sortie va vers un terminal, elle est vidée à la fin de chaque ligne; sinon, il n'est vidé que lorsque le tampon (8K sur mon système Linux / amd64; pourrait être différent sur le vôtre) est plein.

Si tous les services publics suivaient la règle générale, vous verriez la sortie retardée dans tous vos exemples ( cat|sed, cat|tret cat|tr|sed). Mais il y a une exception: GNU catne met jamais sa sortie en mémoire tampon. Soit il n'utilise pas, stdiosoit il modifie la stdiopolitique de mise en mémoire tampon par défaut .

Je peux être sûr que vous utilisez GNU catet pas un autre Unix catparce que les autres ne se comporteraient pas de cette façon. Unix traditionnel cata une -uoption pour demander une sortie sans tampon. GNU catignore l' -uoption car sa sortie est toujours sans tampon.

Ainsi, chaque fois que vous avez un tuyau avec un catà gauche, dans le système GNU, le passage des données à travers le tuyau ne sera pas retardé. Le catne va même pas ligne par ligne - votre terminal le fait. Pendant que vous tapez une entrée pour cat, votre terminal est en mode "canonique" - basé sur la ligne, avec des touches d'édition comme backspace et ctrl-U vous offrant la possibilité de modifier la ligne que vous avez tapée avant de l'envoyer Enter.

Dans l' cat|tr|sedexemple, trreçoit toujours des données catdès que vous appuyez sur Enter, mais trsuit la stdiostratégie par défaut: sa sortie va dans un tube, donc elle ne videra pas après chaque ligne. Il écrit dans le deuxième canal lorsque le tampon est plein ou lorsqu'un EOF est reçu, selon la première éventualité.

sedsuit également la stdiopolitique par défaut, mais sa sortie va à un terminal, il écrira donc chaque ligne dès qu'il en aura fini avec elle. Cela a un effet sur la quantité que vous devez taper avant que quelque chose n'apparaisse à l'autre extrémité du pipeline - si la sedmise en mémoire tampon de sa sortie était bloquée, vous devriez taper deux fois plus (pour remplir trle tampon de sortie et sed la sortie de tampon).

GNU seda une -uoption, donc si vous inversez l'ordre et l'utilisez, cat|sed -u|trvous verrez la sortie apparaître de nouveau instantanément. (L' sed -uoption pourrait être disponible ailleurs mais je ne pense pas que ce soit une ancienne tradition unix comme cat -u) Pour autant que je sache, il n'y a pas d'option équivalente pour tr.

Il existe un utilitaire appelé stdbufqui vous permet de modifier le mode de mise en mémoire tampon de toute commande qui utilise les stdiovaleurs par défaut. C'est un peu fragile car il sert LD_PRELOADà accomplir quelque chose que la bibliothèque C n'a pas été conçue pour prendre en charge, mais dans ce cas, cela semble fonctionner:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

la source
1
Merci! Réponse géniale. Je devrais probablement mentionner la mise en mémoire tampon dans la question d'une manière, afin que l'on puisse le trouver.
xealits
teeet ddjouent généralement selon leurs propres règles. Lorsqu'ils sont combinés de manière imaginative, les trois outils peuvent à peu près de façon portative annuler tout besoin de stdbufpipelines en arrière-plan.
mikeserv
1
C'est une des raisons pour éviter une utilisation inutile du chat .
hobbs
8

En fait, cela m'a pris un peu de réflexion pour comprendre et encore plus pour répondre. Grande question (je voterai ensuite).

Vous avez négligé d'essayer tr | seddans vos éléments de débogage ci-dessus:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Donc évidemment des trtampons. Apprendre quelque chose de nouveau chaque jour!

MODIFIER :

En y réfléchissant, nous avons isolé la cause, mais sans fournir d'explication. Si vous cat | tr, il écrit tout de suite, si vous cat | sed, il écrit tout de suite, mais si vous tr | sed, il attend pour EOF. Je suggère que la réponse pourrait être enterré dans trou le sedcode source alors, et non un problème de tuyauterie.

MODIFIER :

Je vois que Wumpus a fourni l'explication pendant que je tapais la dernière modification. Merci!

Poisson Aerohead
la source
1
en effet ils tamponnent! et le test avec environ 8 Ko de lignes, comme l'a mentionné Wumpus, montre que le tampon est en effet de 8 Ko. J'aimerais accepter les deux réponses pour partager une certaine réputation, mais je considérerai celle de Wumpus comme plus complète. Merci quand même!
xealits
1
Pas de problème, la mienne était la réponse empirique, la sienne était la bonne.
Poisson Aerohead
Voir aussi cette question qui montre comment utiliser stdbufce qui pourrait également être utile. unix.stackexchange.com/questions/182537/…
Joe