Supposons que j'exécute certains processus:
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
Je lance le script ci-dessus comme suit:
foobarbaz | cat
pour autant que je sache, lorsque l'un des processus écrit sur stdout / stderr, leur sortie ne s'entrelace jamais - chaque ligne de stdio semble être atomique. Comment ça marche? Quel utilitaire contrôle la façon dont chaque ligne est atomique?
Réponses:
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:
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
écritaaaa
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
écritbbbb
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.
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:
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 avecstdbuf -oL
.unbuffer
. Certains programmes peuvent se comporter différemment d'autres manières, par exemplegrep
utilise des couleurs par défaut si sa sortie est un terminal.--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.
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 etbar
veut écrire 3 octets, alors un entrelacement possible est P octets defoo
, puis 3 octets debar
, et P octets defoo
.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 avecaaaa
et un seula
. D'où une ligne avec ce seula
suivi d'une écriture de grep, donnant une ligne avecabbbb
.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 avecComment 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.
la source
cat
atomiquement, de sorte que le processus cat reçoive des lignes entières de foo / bar / baz mais pas une demi-ligne d'une et une demi-ligne d'une autre, etc. Puis-je faire quelque chose avec le script bash?awk
deux (ou plus) lignes de sortie ont été produites pour le même ID,find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
mais avecfind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
cela, il n'a correctement produit qu'une seule ligne pour chaque ID.http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P a examiné ceci:
la source
xargs echo
n'appelle pas la fonction intégrée echo bash, mais l'echo
utilitaire de$PATH
. Et de toute façon je ne peux pas reproduire ce comportement d'écho bash avec bash 4.4. Sous Linux, les écritures sur un tube (pas / dev / null) plus grand que 4K ne sont pas garanties d'être atomiques.