web-dev-qa-db-fra.com

Désactiver la mise en mémoire tampon dans le tuyau

J'ai un script qui appelle deux commandes:

long_running_command | print_progress

Le long_running_command imprime les progrès mais je n'en suis pas satisfait. J'utilise print_progress pour le rendre plus agréable (à savoir, j'imprime la progression sur une seule ligne).

Le problème: La connexion d'un tube à stdout active également un tampon 4K, donc le programme d'impression Nice ne reçoit rien ... rien ... rien ... dans son ensemble beaucoup ... :)

Comment désactiver le 4K tampon pour le long_running_command (non, je n'ai pas la source)?

409
Aaron Digulla

Vous pouvez utiliser la commande unbuffer (qui fait partie du package expect ), par exemple.

unbuffer long_running_command | print_progress

unbuffer se connecte à long_running_command via un pseudoterminal (pty), ce qui oblige le système à le traiter comme un processus interactif, n'utilisant donc pas la mise en mémoire tampon de 4 kio dans le pipeline qui est la cause probable du retard.

Pour les pipelines plus longs, vous devrez peut-être annuler la mise en mémoire tampon de chaque commande (sauf la dernière), par exemple.

unbuffer x | unbuffer -p y | z
262
cheduardo

Une autre façon d'habiller ce chat est d'utiliser le programme stdbuf , qui fait partie du GNU Coreutils (FreeBSD a aussi le sien)).

stdbuf -i0 -o0 -e0 command

Cela désactive complètement la mise en mémoire tampon des entrées, des sorties et des erreurs. Pour certaines applications, la mise en mémoire tampon de ligne peut être plus appropriée pour des raisons de performances:

stdbuf -oL -eL command

Notez que cela ne fonctionne que pour la mise en mémoire tampon stdio (printf(), fputs()...) pour les applications liées dynamiquement, et uniquement si cette application n'ajuste pas autrement la mise en mémoire tampon de ses flux standard par lui-même, bien que cela devrait couvrir la plupart des applications.

474
a3nm

Encore une autre façon d'activer le mode de sortie de mise en mémoire tampon de ligne pour le long_running_command consiste à utiliser la commande script qui exécute votre long_running_command dans un pseudo terminal (pty).

script -q /dev/null long_running_command | print_progress      # FreeBSD, Mac OS X
script -c "long_running_command" /dev/null | print_progress    # Linux
78
chad

Pour grep, sed et awk, vous pouvez forcer la sortie à être mise en mémoire tampon de ligne. Vous pouvez utiliser:

grep --line-buffered

Force la sortie à être mise en mémoire tampon de ligne. Par défaut, la sortie est mise en mémoire tampon de ligne lorsque la sortie standard est une borne et un bloc en mémoire tampon dans le cas contraire.

sed -u

Rendre la ligne de sortie en mémoire tampon.

Voir cette page pour plus d'informations: http://www.perkin.org.uk/posts/how-to-fix-stdio-buffering.html

69
yaneku

S'il s'agit d'un problème avec la libc modifiant sa mise en mémoire tampon/vidage lorsque la sortie ne va pas à un terminal, vous devriez essayer socat . Vous pouvez créer un flux bidirectionnel entre presque tout type de mécanisme d'E/S. L'un d'eux est un programme fourchu parlant à un pseudo tty.

 socat EXEC:long_running_command,pty,ctty STDIO 

Ce qu'il fait c'est

  • créer un pseudo tty
  • fork long_running_command avec le côté esclave du pty comme stdin/stdout
  • établir un flux bidirectionnel entre le côté maître du pty et la deuxième adresse (ici c'est STDIO)

Si cela vous donne la même sortie que long_running_command, vous pouvez continuer avec un tuyau.

Edit: Wow Vous n'avez pas vu la réponse sans tampon! Eh bien, socat est un excellent outil de toute façon, donc je pourrais simplement laisser cette réponse

52
shodanex

Vous pouvez utiliser

long_running_command 1>&2 |& print_progress

Le problème est que libc mettra le tampon en ligne lors de la sortie standard vers l'écran et le tampon complet lors de la sortie standard vers un fichier. Mais pas de tampon pour stderr.

Je ne pense pas que ce soit le problème avec le buffer de pipe, c'est tout à propos de la politique de buffer de libc.

20
Wang HongQin

Auparavant, et c'est probablement toujours le cas, lorsque la sortie standard est écrite sur un terminal, elle est mise en mémoire tampon par défaut - lorsqu'une nouvelle ligne est écrite, la ligne est écrite sur le terminal. Lorsque la sortie standard est envoyée à un canal, elle est entièrement tamponnée - les données ne sont donc envoyées au processus suivant dans le pipeline que lorsque le tampon d'E/S standard est rempli.

C'est la source du problème. Je ne sais pas s'il y a beaucoup à faire pour y remédier sans modifier l'écriture du programme dans le canal. Vous pouvez utiliser la fonction setvbuf() avec l'indicateur _IOLBF Pour mettre inconditionnellement stdout en mode ligne tampon. Mais je ne vois pas de moyen facile d'imposer cela sur un programme. Ou le programme peut faire fflush() aux points appropriés (après chaque ligne de sortie), mais le même commentaire s'applique.

Je suppose que si vous remplaciez le tube par un pseudo-terminal, la bibliothèque d'E/S standard penserait que la sortie était un terminal (car c'est un type de terminal) et alignerait automatiquement le tampon. C'est cependant une façon complexe de gérer les choses.

11
Jonathan Leffler

Je sais que c'est une vieille question et a déjà eu beaucoup de réponses, mais si vous souhaitez éviter le problème de tampon, essayez simplement quelque chose comme:

stdbuf -oL tail -f /var/log/messages | tee -a /home/your_user_here/logs.txt

Cela produira en temps réel les journaux et les enregistrera également dans le logs.txt et le tampon n'affectera plus le tail -f commande.

7
Marin Nedea

Je ne pense pas que le problème soit avec le tuyau. Il semble que votre processus de longue durée ne nettoie pas suffisamment son propre tampon. Changer la taille de la mémoire tampon du tube serait un hack pour le contourner, mais je ne pense pas que ce soit possible sans reconstruire le noyau - quelque chose que vous ne voudriez pas faire en tant que hack, car cela affecte probablement beaucoup d'autres processus.

5
anon

Dans la même veine que réponse de chad , vous pouvez écrire un petit script comme celui-ci:

# save as ~/bin/scriptee, or so
script -q /dev/null sh -c 'exec cat > /dev/null'

Utilisez ensuite cette commande scriptee en remplacement de tee.

my-long-running-command | scriptee

Hélas, je n'arrive pas à obtenir une version comme celle-ci pour fonctionner parfaitement sous Linux, semble donc limitée aux Unix de style BSD.

Sous Linux, c'est proche, mais vous ne récupérez pas votre invite à la fin (jusqu'à ce que vous appuyiez sur Entrée, etc.) ...

script -q -c 'cat > /proc/self/fd/1' /dev/null
3
jwd

Selon ce post ici , vous pouvez essayer de réduire le canal ulimit à un seul bloc de 512 octets. Cela ne désactivera certainement pas la mise en mémoire tampon, mais bien, 512 octets est bien moins que 4K: 3

2
RAKK

J'ai trouvé cette solution intelligente: (echo -e "cmd 1\ncmd 2" && cat) | ./Shell_executable

Cela fait l'affaire. cat lira une entrée supplémentaire (jusqu'à EOF) et la transmettra au tube après que echo aura placé ses arguments dans le flux d'entrée de Shell_executable.

1
jaggedsoft

Selon this la taille du buffer de pipe semble être définie dans le noyau et vous obligerait à recompiler votre noyau pour le modifier.

0
second