Ils s'entrelacent! Vous n'avez essayé que de courtes rafales de sortie, qui restent non divisées, mais dans la pratique, il est difficile de garantir qu'une sortie particulière reste non fractionnée.
Mise en mémoire tampon de sortie
Cela dépend de la façon dont les programmes tamponnent leur sortie. La bibliothèque stdio que la plupart des programmes utilisent lors de l'écriture utilise des tampons pour rendre la sortie plus efficace. Au lieu de sortir des données dès que le programme appelle une fonction de bibliothèque pour écrire dans un fichier, la fonction stocke ces données dans un tampon et ne sort réellement les données qu'une fois le tampon rempli. Cela signifie que la sortie se fait par lots. Plus précisément, il existe trois modes de sortie:
- Sans tampon: les données sont écrites immédiatement, sans utiliser de tampon. Cela peut être lent si le programme écrit sa sortie en petits morceaux, par exemple caractère par caractère. Il s'agit du mode par défaut pour l'erreur standard.
- Entièrement tamponné: les données ne sont écrites que lorsque le tampon est plein. Il s'agit du mode par défaut lors de l'écriture dans un canal ou dans un fichier normal, sauf avec stderr.
- Mise en mémoire tampon de ligne: les données sont écrites après chaque nouvelle ligne ou lorsque la mémoire tampon est pleine. Il s'agit du mode par défaut lors de l'écriture sur un terminal, sauf avec stderr.
Les programmes peuvent reprogrammer chaque fichier pour se comporter différemment et vider explicitement le tampon. Le tampon est vidé automatiquement lorsqu'un programme ferme le fichier ou se ferme normalement.
Si tous les programmes qui écrivent dans le même canal utilisent le mode ligne tamponnée, ou utilisent le mode sans tampon et écrivent chaque ligne avec un seul appel à une fonction de sortie, et si les lignes sont suffisamment courtes pour écrire en un seul morceau, alors la sortie sera un entrelacement de lignes entières. Mais si l'un des programmes utilise le mode entièrement tamponné, ou si les lignes sont trop longues, vous verrez des lignes mixtes.
Voici un exemple où j'entrelace la sortie de deux programmes. J'ai utilisé GNU coreutils sur Linux; différentes versions de ces utilitaires peuvent se comporter différemment.
yes aaaa
écrit aaaa
pour toujours dans ce qui est essentiellement équivalent au mode ligne-tampon. L' yes
utilitaire écrit en fait plusieurs lignes à la fois, mais chaque fois qu'il émet une sortie, la sortie est un nombre entier de lignes.
echo bbbb; done | grep b
écrit bbbb
pour toujours en mode entièrement tamponné. Il utilise une taille de tampon de 8192 et chaque ligne fait 5 octets de long. Étant donné que 5 ne divise pas 8192, les frontières entre les écritures ne sont généralement pas à une frontière de ligne.
Posons-les ensemble.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Comme vous pouvez le voir, oui parfois la grep interrompue et vice versa. Seulement environ 0,001% des lignes ont été interrompues, mais c'est arrivé. La sortie est randomisée donc le nombre d'interruptions variera, mais j'ai vu au moins quelques interruptions à chaque fois. Il y aurait une fraction plus élevée de lignes interrompues si les lignes étaient plus longues, car la probabilité d'une interruption augmente à mesure que le nombre de lignes par tampon diminue.
Il existe plusieurs façons d' ajuster la mise en mémoire tampon de sortie . Les principaux sont:
- Désactivez la mise en mémoire tampon dans les programmes qui utilisent la bibliothèque stdio sans modifier ses paramètres par défaut avec le programme
stdbuf -o0
trouvé dans GNU coreutils et certains autres systèmes tels que FreeBSD. Vous pouvez également basculer vers la mise en mémoire tampon de ligne avec stdbuf -oL
.
- Passez à la mise en mémoire tampon de ligne en dirigeant la sortie du programme via un terminal créé spécialement à cet effet avec
unbuffer
. Certains programmes peuvent se comporter différemment d'autres manières, par exemple grep
utilise des couleurs par défaut si sa sortie est un terminal.
- Configurez le programme, par exemple en passant
--line-buffered
à GNU grep.
Voyons à nouveau l'extrait ci-dessus, cette fois avec la mise en mémoire tampon des lignes des deux côtés.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Donc, cette fois, oui n'a jamais interrompu grep, mais grep a parfois interrompu oui. Je reviendrai sur pourquoi plus tard.
Entrelacement de tuyaux
Tant que chaque programme produit une ligne à la fois et que les lignes sont suffisamment courtes, les lignes de sortie seront soigneusement séparées. Mais il y a une limite à la durée des lignes pour que cela fonctionne. Le tuyau lui-même a un tampon de transfert. Lorsqu'un programme sort sur un canal, les données sont copiées du programme d'écriture dans le tampon de transfert du canal, puis plus tard du tampon de transfert du canal dans le programme de lecture. (Du moins sur le plan conceptuel - le noyau peut parfois optimiser cela en une seule copie.)
S'il y a plus de données à copier qu'il n'y en a dans le tampon de transfert du tube, le noyau copie un tampon à la fois. Si plusieurs programmes écrivent dans le même canal et que le premier programme que le noyau choisit veut écrire plus d'un tampon, il n'y a aucune garantie que le noyau choisira à nouveau le même programme la deuxième fois. Par exemple, si P est la taille du tampon, foo
veut écrire 2 * P octets et bar
veut écrire 3 octets, alors un entrelacement possible est P octets de foo
, puis 3 octets de bar
, et P octets de foo
.
Pour revenir à l'exemple yes + grep ci-dessus, sur mon système, yes aaaa
il arrive d'écrire autant de lignes que possible dans un tampon de 8192 octets en une seule fois. Puisqu'il y a 5 octets à écrire (4 caractères imprimables et la nouvelle ligne), cela signifie qu'il écrit 8190 octets à chaque fois. La taille de la mémoire tampon du canal est de 4096 octets. Il est donc possible d'obtenir 4096 octets de oui, puis une sortie de grep, puis le reste de l'écriture de oui (8190 - 4096 = 4094 octets). 4096 octets laisse de la place pour 819 lignes avec aaaa
et un seul a
. D'où une ligne avec ce seul a
suivi d'une écriture de grep, donnant une ligne avec abbbb
.
Si vous voulez voir les détails de ce qui se passe, alors getconf PIPE_BUF .
vous dira la taille de la mémoire tampon de tuyau sur votre système, et vous pouvez voir une liste complète des appels système effectués par chaque programme avec
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
Comment garantir l'entrelacement de lignes propres
Si les longueurs de ligne sont inférieures à la taille de la mémoire tampon de tuyau, la mise en mémoire tampon de ligne garantit qu'il n'y aura pas de ligne mixte dans la sortie.
Si les longueurs de ligne peuvent être plus grandes, il n'y a aucun moyen d'éviter un mélange arbitraire lorsque plusieurs programmes écrivent dans le même canal. Pour garantir la séparation, vous devez faire en sorte que chaque programme écrive sur un tuyau différent et utilisez un programme pour combiner les lignes. Par exemple, GNU Parallel fait cela par défaut.