2009-07-20 6 views
80

Ich möchte Blender von der Kommandozeile aus über ein Ruby-Skript, das dann verarbeitet die Ausgabe von Mixer Zeile für Zeile, um eine Fortschrittsleiste in einer GUI zu aktualisieren verarbeiten. Es ist nicht wirklich wichtig, dass Blender der externe Prozess ist, dessen Standout ich lesen muss.Kontinuierlich von STDOUT des externen Prozesses in Ruby lesen

Ich kann nicht scheinen, um die Fortschrittsbotschaften abzufangen, die blender normalerweise in die Shell druckt, wenn der Mixer-Prozess noch läuft, und ich habe einige Wege versucht. Ich stehe immer auf den stdout von Mixer nach Blender hat verlassen, nicht während es noch läuft.

Hier ist ein Beispiel für einen fehlgeschlagenen Versuch. Es geht und die ersten 25 Zeilen der Ausgabe von Mixern drucken, aber erst nach dem Mixer Prozess beendet wird:

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:

Um es ein wenig klarer, der Befehl aufgerufen wird Mixer gibt einen Strom von Ausgaben in der Shell zurück, die den Fortschritt anzeigen (Teil 1-16 abgeschlossen usw.). Es scheint, dass jeder Aufruf, die Ausgabe zu "bekommen", blockiert wird, bis der Mixer beendet wird. Das Problem ist, wie man auf diese Ausgabe zugreifen kann, während der Mixer noch läuft, da der Mixer seine Ausgabe in die Shell druckt.

Antwort

161

Ich hatte einige Erfolge bei der Lösung dieses Problems von mir. Hier sind die Details, mit einigen Erklärungen, falls jemand mit einem ähnlichen Problem diese Seite findet. Aber wenn Sie sich nicht für Details interessieren, ist hier die kurze Antwort:

Verwenden Sie PTY.Laich in der folgenden Art und Weise (mit Ihrem eigenen Befehl natürlich):

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 

Und hier ist die lange Antwort, mit viel zu vielen Details:

Das eigentliche Problem, dass, wenn ein Prozess doesn zu sein scheint Lege das stdout nicht explizit weg, dann wird alles, was in stdout geschrieben wurde, gepuffert anstatt tatsächlich gesendet, bis der Prozess abgeschlossen ist, um IO zu minimieren (das ist ein Implementierungsdetail von vielen C-Bibliotheken, so dass der Durchsatz maximiert ist durch weniger häufige IO). Wenn Sie den Prozess leicht so ändern können, dass er stdout regelmäßig löscht, dann wäre das Ihre Lösung. In meinem Fall war es Mixer, also ein bisschen einschüchternd für ein komplettes Noob wie mich, um die Quelle zu modifizieren.

Wenn Sie diese Prozesse jedoch über die Shell ausführen, zeigen sie stdout in Echtzeit zur Shell an, und das stdout scheint nicht gepuffert zu sein. Es wird nur gepuffert, wenn es von einem anderen Prozess aufgerufen wird, aber wenn eine Shell behandelt wird, wird der Standout in Echtzeit ungepuffert angezeigt.

Dieses Verhalten kann sogar mit einem Ruby-Prozess als Child-Prozess beobachtet werden, dessen Ausgabe in Echtzeit gesammelt werden muss. Erstellen Sie einfach ein Skript, random.rb mit folgenden Zeile:

ein Ruby-Skript Dann ist es zu nennen, und seinen Ausgang zurück:

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

Sie werden sehen, dass Sie nicht bekommen, das Ergebnis in Echtzeit, wie Sie vielleicht erwarten, aber gleichzeitig auf einmal. STDOUT wird gepuffert, auch wenn Sie random.rb selbst ausführen, wird es nicht gepuffert. Dies kann durch Hinzufügen einer STDOUT.flush Anweisung innerhalb des Blocks in random.rb gelöst werden. Aber wenn Sie die Quelle nicht ändern können, müssen Sie dies umgehen. Sie können es nicht von außerhalb des Prozesses spülen.

Wenn der Subprozess in Echtzeit in die Shell drucken kann, muss es auch möglich sein, dies mit Ruby in Echtzeit zu erfassen. Und da ist. Sie müssen das PTY-Modul verwenden, das in Ruby-Core enthalten ist (1.8.6 sowieso). Traurig ist, dass es nicht dokumentiert ist. Aber ich habe glücklicherweise einige Beispiele gefunden.

Zuerst, um zu erklären, was PTY ist, steht es für pseudo terminal. Grundsätzlich erlaubt es dem Ruby-Skript, sich dem Unterprozess zu präsentieren, als wäre es ein echter Benutzer, der den Befehl in eine Shell eingegeben hat. Daher tritt jedes geänderte Verhalten auf, das nur auftritt, wenn ein Benutzer den Prozess über eine Shell gestartet hat (z. B. wenn STDOUT in diesem Fall nicht gepuffert wird). Das Verbergen der Tatsache, dass ein anderer Prozess gestartet wurde, ermöglicht es Ihnen, den STDOUT in Echtzeit zu sammeln, da er nicht gepuffert wird.

Um diese Arbeit mit dem random.rb Skript als Kind zu machen, versuchen Sie den folgenden Code ein:

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 
+0

+1 - Schön und gut gemacht! :) –

+6

Das ist so eine gute Antwort. Meinen Tag gerettet. –

+7

Das ist großartig, aber ich glaube, die stdin und stdout Block Parameter sollten vertauscht werden. Siehe: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/PTY.html#method-c-spawn –

4

STDOUT.flush oder STDOUT.sync = true

+0

ja, das war eine lahme Antwort. Deine Antwort war besser. – mveerman

+0

'STDOUT.sync = true' arbeitete für wie ein Charme für mich –

12

Verwendung IO.popen. This ist ein gutes Beispiel.

Ihr Code würde so etwas wie:

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 
+0

Ich habe es versucht. Das Problem ist das gleiche. Ich bekomme danach Zugriff auf die Ausgabe. Ich glaube, dass IO.popen das erste Argument als Befehl ausführt und darauf wartet, dass es beendet wird. In meinem Fall wird die Ausgabe vom Mixer ausgegeben, während der Mixer noch verarbeitet wird. Und dann wird der Block nachher aufgerufen, was mir nicht weiterhilft. – ehsanul

+0

Folgendes habe ich versucht. Es gibt die Ausgabe zurück, nachdem der Mixer fertig ist: IO.popen ("blender -b mball.blend // rendert/-F JPEG -x 1 -f 1", "w +") do | blender | blender.each {| Zeile | legt Linie; Ausgabe + = Zeile;} Ende – ehsanul

+3

Ich bin mir nicht sicher, was in Ihrem Fall passiert. Ich habe den obigen Code mit 'yes' getestet, einer Befehlszeilenanwendung, die niemals _ends_, und es hat funktioniert. Der Code war wie folgt: 'IO.popen ('ja') {| p | p.each {| f | setzt f}} '. Ich vermute, dass es etwas mit Blender zu tun hat und nicht mit Rubin. Vermutlich bläst Blender nicht immer seinen STDOUT. –

4

Blender wahrscheinlich nicht drucken Zeilenumbrüche, bis er das Programm endet. Stattdessen wird das Wagenrücklaufzeichen (\ r) gedruckt. Die einfachste Lösung ist wahrscheinlich die Suche nach der magischen Option, die Zeilenumbrüche mit der Fortschrittsanzeige druckt.

Das Problem ist, dass IO#gets (und verschiedene andere IO-Methoden) den Zeilenumbruch als Trennzeichen verwenden. Sie werden den Stream lesen, bis sie das "\ n" -Zeichen treffen (welcher Blender nicht sendet).

Versuchen Sie, das Eingabetrennzeichen $/ = "\r" zu setzen oder stattdessen blender.gets("\r") zu verwenden.

BTW, für solche Probleme sollten Sie immer überprüfen puts someobj.inspect oder p someobj (die beide das gleiche tun), um versteckte Zeichen innerhalb der Zeichenfolge zu sehen.

+1

Ich habe gerade die Ausgabe überprüft, und es scheint, dass Blender einen Zeilenumbruch (\ n) verwendet, so dass das nicht das Problem war. Vielen Dank für den Tipp, ich werde es beim nächsten Mal im Hinterkopf behalten. – ehsanul

0

Ich weiß nicht, ob zu dem Zeitpunkt ehsanul die Frage beantwortet, gab es Open3::pipeline_rw() noch vorhanden, aber es macht die Dinge wirklich einfacher.

Ich verstehe ehsanuls Arbeit mit Blender nicht, also machte ich ein anderes Beispiel mit tar und xz. tar wird die Eingabedatei (en) in den Stdout-Stream einfügen, dann xz nehmen Sie das stdout und komprimieren Sie es erneut zu einem anderen Stdout.Unsere Aufgabe ist es, das letzte Standout zu nehmen und es in unsere endgültige Datei zu schreiben:

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 
Verwandte Themen