2009-07-20 18 views
80

Voglio eseguire Blender dalla riga di comando attraverso uno script ruby, che elaborerà quindi l'output dato da Blender riga per riga per aggiornare una barra di avanzamento in una GUI. Non è davvero importante che Blender sia il processo esterno il cui stdout devo leggere.Continua a leggere da STDOUT del processo esterno in Ruby

Non riesco a essere in grado di catturare i messaggi di avanzamento Il frullatore normalmente stampa sulla shell quando il processo di frullatore è ancora in esecuzione, e ho provato alcuni modi. Mi sembra sempre di accedere allo stdout del frullatore dopo che si è chiuso, non mentre è ancora in esecuzione.

Ecco un esempio di tentativo fallito. Si riempie e stampare le prime 25 righe del uscita del frullatore, ma solo dopo che il processo frullatore è uscito:

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:

per renderlo un po 'più chiaro, il comando frullatore invocando restituisce un flusso di output nella shell, che indica lo stato di avanzamento (parte 1-16 completata, ecc.). Sembra che ogni chiamata a "ottenere" l'output sia bloccata fino a quando si blocca. Il problema è come ottenere l'accesso a questo output mentre Blender è ancora in esecuzione, poiché Blender stampa l'output in shell.

risposta

161

Ho avuto un certo successo nel risolvere questo mio problema. Ecco i dettagli, con alcune spiegazioni, nel caso qualcuno che abbia un problema simile trovi questa pagina. Ma se non ti interessa i dettagli, , ecco la risposta breve:

Utilizzare PTY.uova nel modo seguente (con il proprio comando del corso):

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 

E ecco la risposta lunga, con troppi dettagli:

Il vero problema sembra essere che se un processo doesn sciacquare esplicitamente il suo stdout, quindi tutto ciò che viene scritto su stdout viene memorizzato sul buffer piuttosto che effettivamente inviato, fino a quando il processo non viene eseguito, in modo da minimizzare l'IO (questo è apparentemente un dettaglio di implementazione di molte librerie C, realizzato in modo tale che il throughput sia massimizzato tramite IO meno frequente). Se è possibile modificare facilmente il processo in modo che stia scaricando regolarmente stdout, questa sarebbe la soluzione. Nel mio caso, era un frullatore, quindi un po 'intimidatorio per un noob completo come me per modificare la fonte.

Ma quando si eseguono questi processi dalla shell, visualizzano lo stdout nella shell in tempo reale e lo stdout non sembra essere memorizzato nel buffer. È bufferizzato solo quando viene chiamato da un altro processo, ma se viene gestita una shell, lo stdout viene visto in tempo reale, senza buffer.

Questo comportamento può anche essere osservato con un processo ruby ​​come processo secondario il cui output deve essere raccolto in tempo reale. Basta creare uno script, random.rb, con la seguente riga:

Poi uno script Ruby per chiamarlo e restituisce la sua uscita:

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

Vedrai che non si ottiene il risultato in tempo reale come ci si potrebbe aspettare, ma tutto in una volta dopo. STDOUT è in fase di buffering, anche se si esegue random.rb da soli, non viene memorizzato nel buffer. Questo può essere risolto aggiungendo un'istruzione STDOUT.flush all'interno del blocco in random.rb. Ma se non puoi cambiare la fonte, devi aggirare questo. Non puoi svuotarlo dall'esterno del processo.

Se il sottoprocesso può stampare in shell in tempo reale, deve esserci un modo per catturarlo con Ruby anche in tempo reale. E c'è. Devi usare il modulo PTY, incluso nel ruby ​​core, credo (1.8.6 comunque). La cosa triste è che non è documentato. Ma ho trovato alcuni esempi di utilizzo per fortuna.

Innanzitutto, per spiegare cos'è PTY, corrisponde a pseudo terminal. Fondamentalmente, consente allo script ruby ​​di presentarsi al sottoprocesso come se fosse un utente reale che ha appena digitato il comando in una shell. Quindi qualsiasi comportamento alterato che si verifica solo quando un utente ha avviato il processo attraverso una shell (come lo STDOUT non viene bufferizzato, in questo caso) si verificherà. Nascondendo il fatto che un altro processo ha avviato questo processo consente di raccogliere lo STDOUT in tempo reale, poiché non viene memorizzato nel buffer.

per fare questo lavoro con lo script random.rb come il bambino, provare il seguente codice:

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 - Bello e ben fatto! :) –

+6

Questa è una buona risposta. Ha reso la mia giornata. –

+7

Questo è ottimo, ma credo che i parametri del blocco stdin e stdout dovrebbero essere scambiati. Vedi: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/pty/rdoc/PTY.html#method-c-spawn –

4

STDOUT.flush o STDOUT.sync = true

+0

sì, questa era una risposta zoppa. La tua risposta è stata migliore. – mveerman

+0

'STDOUT.sync = true' ha funzionato come un fascino per me –

12

uso IO.popen. This è un buon esempio.

Il tuo codice sarebbe diventato qualcosa di simile:

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

Ho provato questo. Il problema è lo stesso. In seguito, ho accesso all'output. Credo che IO.popen inizi eseguendo il primo argomento come comando e aspetta che finisca. Nel mio caso, l'output è dato da Blender mentre il frullatore è ancora in elaborazione. E poi il blocco viene invocato dopo, il che non mi aiuta. – ehsanul

+0

Ecco cosa ho provato. Restituisce l'output dopo aver completato il frullatore: IO.popen ("blender -b mball.blend // rendering/-F JPEG -x 1 -f 1", "w +") do | blender | blender.each {| line | mette la linea; output + = line;} fine – ehsanul

+3

Non sono sicuro di ciò che sta accadendo nel tuo caso. Ho provato il codice sopra con 'yes', un'applicazione da riga di comando che non ha mai _ends_, e ha funzionato. Il codice era il seguente: 'IO.popen ('yes') {| p | p.each {| f | mette f}} '. Sospetto che sia qualcosa che ha a che fare con il frullatore e non con il rubino. Probabilmente il frullatore non scarica sempre il suo STDOUT. –

4

Blender probabilmente non stampa interruzioni di linea fino a quando non sta finendo il programma. Invece, sta stampando il carattere di ritorno a capo (\ r). La soluzione più semplice è probabilmente la ricerca dell'opzione magica che stampa le interruzioni di riga con l'indicatore di avanzamento.

Il problema è che IO#gets (e vari altri metodi IO) utilizzano l'interruzione di riga come delimitatore. Loro leggeranno il flusso finché non colpiranno il carattere "\ n" (che il frullatore non sta inviando).

Provare a impostare il separatore di ingresso $/ = "\r" o utilizzare blender.gets("\r").

BTW, per problemi come questi, è necessario controllare sempre puts someobj.inspect o p someobj (entrambi che fanno la stessa cosa) per vedere eventuali caratteri nascosti all'interno della stringa.

+1

Ho appena controllato l'output fornito e sembra che Blender utilizzi un'interruzione di riga (\ n), quindi non era questo il problema. Grazie comunque per il suggerimento, lo terrò a mente la prossima volta che eseguirò il debug di qualcosa del genere. – ehsanul

0

non so se al momento ehsanul ha risposto alla domanda, c'era Open3::pipeline_rw() ancora disponibile, ma rende davvero le cose più semplici.

Non capisco il lavoro di ehsanul con Blender, quindi ho fatto un altro esempio con tar e xz. tar aggiungerà i file di input allo stream stdout, quindi lo xz prenderà quello stdout e lo comprime, nuovamente, in un altro stdout.Il nostro compito è prendere l'ultimo stdout e scriverlo nel nostro file finale:

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 
Problemi correlati