Lecture continue de STDOUT du processus externe en Ruby

Je veux lancer Blender depuis la ligne de commande via un script Ruby, qui traitera ensuite la sortie donnée par Blender ligne par ligne pour mettre à jour une barre de progression dans une interface graphique. Il n’est pas vraiment important que Blender soit le processus externe dont je dois lire la sortie standard.

Je n’arrive pas à capter les messages d’avancement que Blender imprime normalement sur le shell lorsque le processus de mixage est toujours en cours, et j’ai essayé plusieurs méthodes. J’ai toujours l’air d’avoir access à la sortie du mélangeur une fois que le mélangeur s’est arrêté, pas pendant qu’il fonctionne encore.

Voici un exemple de tentative infructueuse. Il obtient et imprime les 25 premières lignes de la sortie du mélangeur, mais seulement après la fin du processus de mixage:

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} 

Modifier:

Pour le rendre un peu plus clair, la commande invoquant le mélangeur restitue un stream de sortie dans le shell, indiquant la progression (partie 1-16 terminée, etc.). Il semble que tout appel à “obtenir” la sortie soit bloqué jusqu’à ce que Blender se ferme. Le problème est de savoir comment accéder à cette sortie alors que Blender est toujours en cours d’exécution, à mesure que Blender imprime sa sortie sur shell.

J’ai réussi à résoudre ce problème. Voici les détails, avec quelques explications, au cas où quelqu’un ayant un problème similaire trouve 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 # Do stuff with the output here. Just printing to show it works 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 longue réponse , avec beaucoup trop de détails:

Le vrai problème semble être que si un processus ne vide pas explicitement sa sortie standard, tout ce qui est écrit dans stdout est mis en mémoire tampon plutôt qu’en réalité, jusqu’à ce que le processus soit terminé, de manière à minimiser les E / S. Bibliothèques C, conçues pour maximiser le débit grâce à des entrées-sorties moins fréquentes). Si vous pouvez facilement modifier le processus pour qu’il vide régulièrement la sortie standard, ce serait votre solution. Dans mon cas, c’était mélangeur, donc un peu intimidant pour un noob complet comme moi de modifier la source.

Mais lorsque vous exécutez ces processus à partir du shell, ils affichent stdout sur le shell en temps réel, et la sortie standard ne semble pas être mise en mémoire tampon. Je crois que c’est seulement tamponné quand on l’appelle d’un autre processus, 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 ​​en tant que 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 vous en doutez, mais tout de suite après. STDOUT est mis en mémoire tampon, même si vous exécutez random.rb vous-même, il n’est pas mis en mémoire tampon. Cela peut être résolu en ajoutant une instruction STDOUT.flush dans le bloc 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 en shell en temps réel, il doit également y avoir un moyen de le capturer en temps réel avec Ruby. Et voici. Vous devez utiliser le module PTY, inclus dans ruby ​​core je crois (1.8.6 de toute façon). Ce qui est sortingste, c’est que ce n’est pas documenté. Mais j’ai trouvé quelques exemples d’utilisation heureusement.

Tout d’abord, pour expliquer ce qu’est le PTY, cela signifie pseudo-terminal . Fondamentalement, il permet au script ruby ​​de se présenter au sous-processus comme s’il s’agissait d’un utilisateur réel qui venait de saisir la commande dans un shell. Tout comportement altéré qui se produit uniquement lorsqu’un utilisateur a démarré le processus via un shell (tel que le STDOUT qui n’est pas mis en mémoire tampon, dans ce cas) se produira. En masquant 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 

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 

STDOUT.flush ou STDOUT.sync = true

Blender n’imprime probablement pas les sauts de ligne avant la fin du programme. Au lieu de cela, il imprime le caractère de retour chariot (\ r). La solution la plus simple consiste probablement à rechercher l’option magique qui imprime des sauts de ligne avec l’indicateur de progression.

Le problème est que IO#gets (et diverses autres méthodes IO) utilisent le saut de ligne comme un délimiteur. Ils liront le stream jusqu’à ce qu’ils atteignent le caractère “\ n” (que Blender n’envoie pas).

Essayez de définir le séparateur d’entrée $/ = "\r" ou utilisez blender.gets("\r") place.

BTW, pour des problèmes tels que ceux-ci, vous devriez toujours vérifier les éléments puts someobj.inspect ou p someobj (qui font tous deux la même chose) pour voir tous les caractères cachés dans la chaîne.

Je ne sais pas si ehsanul a répondu à la question, il y avait encore Open3::pipeline_rw() , 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 et xz . tar appenda le (s) fichier (s) d’entrée au stream stdout, puis xz prendra cette stdout et la compressera à nouveau dans une autre sortie. Notre travail consiste à prendre la dernière sortie et à 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