88 votes

Lecture continue de STDOUT du processus externe en Ruby

Je veux lancer blender à partir de la ligne de commande par l'intermédiaire d'un script ruby, qui sera ensuite procédé à la sortie donnée par blender, ligne par ligne, de mettre à jour une barre de progression dans une interface graphique. Ce n'est pas vraiment important que blender est le processus externe, dont la sortie standard (stdout) j'ai besoin de lire.

Je ne peux pas semblent être en mesure d'intercepter les messages de progression blender normalement imprime à la coque lorsque le mélangeur processus est toujours en cours, et j'ai essayé de plusieurs façons. J'ai toujours l'impression d'accéder à la sortie standard (stdout) de blender après blender a quitté, pas pendant qu'il est encore en cours d'exécution.

Voici un exemple de l'échec d'une tentative. Il ne obtenir et d'imprimer les 25 premières lignes de la sortie de blender, mais seulement après que le blender processus a quitté:

blender = nil
t = Thread.new do
  blender = open "| blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1"
end
puts "Blender is doing its job now..."
25.times { puts blender.gets}

Edit:

Pour le rendre un peu plus clair, la commande en invoquant blender donne un flux de sortie dans le shell, en indiquant les progrès (partie 1-16 remplis, etc). Il semble que tout appel à "obtient" la sortie est bloquée jusqu'à ce mélangeur se ferme. La question est de savoir comment obtenir l'accès à cette sortie alors que blender est toujours en cours d'exécution, comme blender imprime sortie de la coquille.

179voto

ehsanul Points 3103

J'ai eu un certain succès dans la résolution de ce problème de la mine. Voici les détails, avec quelques explications, au cas où quelqu'un ayant un problème similaire trouve cette page. Mais si vous n'avez pas de soins pour les détails, voici la réponse:

Utilisation PTY.spawn de la manière suivante (avec votre commande, bien sûr):

require 'pty'
cmd = "blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1" 
begin
  PTY.spawn( cmd ) do |stdin, stdout, pid|
    begin
      # Do stuff with the output here. Just printing to show it works
      stdin.each { |line| print line }
    rescue Errno::EIO
      puts "Errno:EIO error, but this probably just means " +
            "that the process has finished giving output"
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end

Et voici la réponse longue, avec beaucoup trop de détails:

Le vrai problème semble être que si un processus n'est pas explicitement vider ses stdout, alors tout ce qui est écrit sur la sortie standard est mis en mémoire tampon plutôt que de réellement envoyé, jusqu'à ce que le processus est terminé, de manière à minimiser les IO (ce qui est apparemment un détail d'implémentation de beaucoup de bibliothèques C, fait en sorte que le débit est optimisée grâce à de moins en moins fréquentes IO). Si vous pouvez facilement modifier le processus, de sorte qu'il bouffées de stdout régulièrement, cela pourrait être votre solution. Dans mon cas, c'était blender, donc un peu intimidant pour un noob complet, comme moi, de modifier le code source.

Mais lors de l'exécution de ces processus à partir de la coquille, ils font de l'affichage sur la sortie standard à la coque en temps réel, et la sortie standard (stdout) ne semble pas être mis en mémoire tampon. C'est seulement tampon lorsqu'il est appelé à partir d'un autre processus, je crois, mais si une coquille est en cours de traitement, la sortie standard (stdout) est visible en temps réel, mémoire sans tampon.

Ce comportement peut même être observée avec un rubis processus que le processus de l'enfant dont la sortie doit être collectées en temps réel. Il suffit de créer un script, aléatoire.rb, avec la ligne suivante:

5.times { |i| sleep( 3*rand ); puts "#{i}" }

Ensuite un script ruby à l'appeler et de retour de sa sortie:

IO.popen( "ruby random.rb") do |random|
    random.each { |line| puts line }
  end
end

Vous verrez que vous n'obtenez pas le résultat en temps réel comme vous vous en doutez, mais tout à la fois par la suite. La sortie standard est en cours de mise en mémoire tampon, même si vous exécutez aléatoire.rb-vous, il n'est pas mis en mémoire tampon. Ce problème peut être résolu par l'ajout d'un STDOUT.flush déclaration à l'intérieur du bloc dans un ordre aléatoire.rb. Mais si vous ne pouvez pas modifier le code source, vous devez travailler autour de cela. Vous ne pouvez pas le rincer à partir de l'extérieur du processus.

Si le sous-processus peuvent imprimer à coque en temps réel, alors il doit y avoir un moyen de capturer ce avec Ruby en temps réel. Et il y est. Vous devez utiliser le PTY module, inclus dans ruby je crois (1.8.6 de toute façon). Triste, c'est que ce n'est pas documentée. Mais j'ai trouvé quelques exemples de l'utilisation heureusement.

D'abord, pour expliquer ce que PTY est, il est synonyme de pseudo-terminal. Fondamentalement, il permet au script ruby, de se présenter à la sous-processus, comme si c'est un utilisateur réel qui a juste tapé la commande dans un shell. Ainsi, tout changement de comportement qui se produit uniquement lorsqu'un utilisateur a commencé le processus par l'intermédiaire d'un interpréteur de commandes (comme le STDOUT de ne pas être mise en mémoire tampon, dans ce cas). Dissimuler le fait qu'un autre processus a commencé ce processus vous permettant de récupérer la sortie standard (STDOUT) en temps réel, car il n'est pas mis en mémoire tampon.

Pour faire ce travail avec l'aléatoire.rb script, comme l'enfant, essayez le code suivant:

require 'pty'
begin
  PTY.spawn( "ruby random.rb" ) do |stdin, stdout, pid|
    begin
      stdin.each { |line| print line }
    rescue Errno::EIO
    end
  end
rescue PTY::ChildExited
  puts "The child process exited!"
end

12voto

Sinan Taifour Points 3878

utilisez IO.popen . Ceci est un bon exemple.

Votre code deviendrait quelque chose comme:

 blender = nil
t = Thread.new do
  IO.popen("blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1") do |blender|
    blender.each do |line|
      puts line
    end
  end
end
 

7voto

mveerman Points 4092

STDOUT.flush ou STDOUT.sync = true

4voto

hhaamu Points 1160

Blender n'a probablement pas d'impression des sauts de lignes jusqu'à la fin du programme. Au lieu de cela, c'est l'impression du caractère de retour chariot (\r). La solution la plus simple est probablement la recherche de la magie option qui imprime des sauts de lignes avec l'indicateur de progression.

Le problème est qu' IO#gets (et divers autres IO méthodes) utilisez le saut de ligne comme séparateur. Ils vont lire le flux jusqu'à ce que ils ont frappé le "\n" personnage (que blender n'est pas d'envoi).

Essayez de réglage de l'entrée du séparateur $/ = "\r" ou à l'aide de blender.gets("\r") à la place.

BTW, pour ces problèmes, vous devriez toujours vérifier puts someobj.inspect ou p someobj (les deux font la même chose) à voir l'un des personnages cachés à l'intérieur de la chaîne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X