J'ai réussi à résoudre mon problème. Voici les détails, avec quelques explications, au cas où quelqu'un ayant un problème similaire trouverait cette page. Mais si vous ne vous souciez pas des détails, voici la réponse courte :
Utilisez PTY.spawn de la manière suivante (avec votre propre commande bien sûr):
require 'pty'
cmd = "blender -b mball.blend -o //renders/ -F JPEG -x 1 -f 1"
begin
PTY.spawn( cmd ) do |stdout, stdin, pid|
begin
stdout.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 ne vide pas explicitement son stdout, alors tout ce qui est écrit sur stdout est mis en mémoire tampon plutôt que réellement envoyé, jusqu'à ce que le processus soit terminé, afin de minimiser les E / S (c'est apparemment un détail d'implémentation de beaucoup Bibliothèques C, conçues pour que le débit soit maximisé grâce à des E / S moins fréquentes). Si vous pouvez facilement modifier le processus pour qu'il vide régulièrement stdout, alors ce serait votre solution. Dans mon cas, c'était blender, donc un peu intimidant pour un noob complet comme moi de modifier la source.
Mais lorsque vous exécutez ces processus depuis le shell, ils affichent stdout vers le shell en temps réel, et le stdout ne semble pas être mis en mémoire tampon. Il est uniquement mis en mémoire tampon lorsqu'il est appelé depuis un autre processus, je crois, mais si un shell est traité, la sortie standard est vue en temps réel, sans tampon.
Ce comportement peut même être observé avec un processus ruby comme le processus enfant dont la sortie doit être collectée en temps réel. Créez simplement un script, random.rb, avec la ligne suivante:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Puis un script ruby pour l'appeler et renvoyer sa sortie:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Vous verrez que vous n'obtenez pas le résultat en temps réel comme vous pourriez vous y attendre, mais tout d'un coup par la suite. STDOUT est mis en mémoire tampon, même si vous exécutez vous-même random.rb, il ne l'est pas. Cela peut être résolu en ajoutant une STDOUT.flush
instruction à l'intérieur du bloc dans random.rb. Mais si vous ne pouvez pas changer la source, vous devez contourner ce problème. Vous ne pouvez pas le vider de l'extérieur du processus.
Si le sous-processus peut imprimer sur le shell en temps réel, il doit également y avoir un moyen de capturer cela avec Ruby en temps réel. Et voici. Vous devez utiliser le module PTY, inclus dans le noyau ruby je crois (1.8.6 de toute façon). Le plus triste, c'est que ce n'est pas documenté. Mais j'ai trouvé quelques exemples d'utilisation heureusement.
Premièrement, pour expliquer ce qu'est PTY, il signifie pseudo terminal . Fondamentalement, cela permet au script ruby de se présenter au sous-processus comme s'il s'agissait d'un utilisateur réel qui venait de taper la commande dans un shell. Ainsi, tout comportement modifié qui se produit uniquement lorsqu'un utilisateur a démarré le processus via un shell (tel que STDOUT n'étant pas mis en mémoire tampon, dans ce cas) se produira. Dissimuler le fait qu'un autre processus a démarré ce processus vous permet de collecter le STDOUT en temps réel, car il n'est pas mis en mémoire tampon.
Pour que cela fonctionne avec le script random.rb en tant qu'enfant, essayez le code suivant:
require 'pty'
begin
PTY.spawn( "ruby random.rb" ) do |stdout, stdin, pid|
begin
stdout.each { |line| print line }
rescue Errno::EIO
end
end
rescue PTY::ChildExited
puts "The child process exited!"
end
STDOUT.sync = true
c'est tout ce dont vous avez besoin (réponse de mveerman ci-dessous). Voici un autre fil avec un exemple de code .utiliser
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
la source
yes
, une application de ligne de commande qui ne se termine jamais , et cela a fonctionné. Le code a été comme suit:IO.popen('yes') { |p| p.each { |f| puts f } }
. Je soupçonne que c'est quelque chose qui a à voir avec le mélangeur, et non avec le rubis. Le mélangeur ne rince probablement pas toujours son STDOUT.STDOUT.flush ou STDOUT.sync = true
la source
STDOUT.sync = true; system('<whatever-command>')
Blender n'imprime probablement pas les sauts de ligne tant qu'il n'a pas terminé le programme. Au lieu de cela, il imprime le caractère de retour chariot (\ r). La solution la plus simple est probablement de rechercher l'option magique qui imprime les sauts de ligne avec l'indicateur de progression.
Le problème est que
IO#gets
(et diverses autres méthodes d'E / S) utilisent le saut de ligne comme délimiteur. Ils liront le flux jusqu'à ce qu'ils atteignent le caractère "\ n" (quel mélangeur n'envoie pas).Essayez de définir le séparateur d'entrée
$/ = "\r"
ou utilisez à lablender.gets("\r")
place.BTW, pour de tels problèmes, vous devez toujours vérifier
puts someobj.inspect
oup someobj
(les deux font la même chose) pour voir tous les caractères cachés dans la chaîne.la source
Je ne sais pas si à l'époque ehsanul a répondu à la question, il y en avait
Open3::pipeline_rw()
encore, mais cela simplifie vraiment les choses.Je ne comprends pas le travail d'ehsanul avec Blender, alors j'ai fait un autre exemple avec
tar
etxz
.tar
ajoutera le (s) fichier (s) d'entrée au flux stdout, puis lexz
prendrastdout
et le compressera, à nouveau, dans un autre stdout. Notre travail est de prendre le dernier stdout et de l'écrire dans notre fichier final:require 'open3' if __FILE__ == $0 cmd_tar = ['tar', '-cf', '-', '-T', '-'] cmd_xz = ['xz', '-z', '-9e'] list_of_files = [...] Open3.pipeline_rw(cmd_tar, cmd_xz) do |first_stdin, last_stdout, wait_threads| list_of_files.each { |f| first_stdin.puts f } first_stdin.close # Now start writing to target file open(target_file, 'wb') do |target_file_io| while (data = last_stdout.read(1024)) do target_file_io.write data end end # open end # pipeline_rw end
la source
Ancienne question, mais avait des problèmes similaires.
Sans vraiment changer mon code Ruby, une chose qui m'a aidé a été d' encapsuler mon tube avec stdbuf , comme ceci:
cmd = "stdbuf -oL -eL -i0 openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}" @xSess = IO.popen(cmd.split " ", mode = "w+")
Dans mon exemple, la commande réelle avec laquelle je souhaite interagir comme s'il s'agissait d'un shell est openssl .
-oL -eL
dites-lui de mettre en mémoire tampon STDOUT et STDERR uniquement jusqu'à une nouvelle ligne. Remplacez complètementL
par0
pour désamorcerCela ne fonctionne pas toujours, cependant: parfois, le processus cible applique son propre type de tampon de flux, comme l'a souligné une autre réponse.
la source