He tenido cierto éxito resolviendo este problema mío. Aquí están los detalles, con algunas explicaciones, en caso de que alguien que tenga un problema similar encuentre esta página. Pero si no le interesan los detalles, aquí está la respuesta corta :
Utilice PTY.spawn de la siguiente manera (con su propio comando, por supuesto):
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
Y aquí está la respuesta larga , con demasiados detalles:
El problema real parece ser que si un proceso no vacía explícitamente su stdout, entonces todo lo que se escribe en stdout se almacena en búfer en lugar de enviarse realmente, hasta que se realiza el proceso, para minimizar IO (esto aparentemente es un detalle de implementación de muchos Bibliotecas C, creadas para maximizar el rendimiento mediante IO menos frecuentes). Si puede modificar fácilmente el proceso para que se descargue con regularidad, entonces esa sería su solución. En mi caso, era blender, por lo que un poco intimidante para un novato como yo, modificar la fuente.
Pero cuando ejecuta estos procesos desde el shell, muestran stdout al shell en tiempo real, y stdout no parece estar almacenado en búfer. Creo que solo se almacena en búfer cuando se llama desde otro proceso, pero si se está tratando con un shell, la salida estándar se ve en tiempo real, sin búfer.
Este comportamiento se puede observar incluso con un proceso ruby como proceso hijo cuya salida debe recopilarse en tiempo real. Simplemente cree un script, random.rb, con la siguiente línea:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Luego, un script ruby para llamarlo y devolver su salida:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Verá que no obtiene el resultado en tiempo real como cabría esperar, sino todo a la vez después. STDOUT se almacena en búfer, aunque si ejecuta random.rb usted mismo, no se almacena en búfer. Esto se puede resolver agregando una STDOUT.flush
declaración dentro del bloque en random.rb. Pero si no puede cambiar la fuente, debe solucionarlo. No puede vaciarlo desde fuera del proceso.
Si el subproceso puede imprimir en shell en tiempo real, entonces debe haber una forma de capturar esto con Ruby también en tiempo real. Y ahí está. Tienes que usar el módulo PTY, incluido en ruby core, creo (1.8.6 de todos modos). Lo triste es que no está documentado. Pero, afortunadamente, encontré algunos ejemplos de uso.
Primero, para explicar qué es PTY, significa pseudo terminal . Básicamente, permite que el script ruby se presente al subproceso como si fuera un usuario real que acaba de escribir el comando en un shell. Por lo tanto, se producirá cualquier comportamiento alterado que ocurra solo cuando un usuario haya iniciado el proceso a través de un shell (como, en este caso, que STDOUT no se almacena en búfer). Ocultar el hecho de que otro proceso ha iniciado este proceso le permite recopilar el STDOUT en tiempo real, ya que no se almacena en búfer.
Para que esto funcione con el script random.rb como hijo, pruebe el siguiente código:
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
es todo lo que se necesita (la respuesta de mveerman a continuación). Aquí hay otro hilo con un código de ejemplo .utilizar
IO.popen
. Este es un buen ejemplo.Tu código se convertiría en algo como:
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
fuente
yes
una aplicación de línea de comandos que nunca termina , y funcionó. El código fue de la siguiente manera:IO.popen('yes') { |p| p.each { |f| puts f } }
. Sospecho que es algo que tiene que ver con Blender y no con ruby. Probablemente la licuadora no siempre descarga su STDOUT.STDOUT.flush o STDOUT.sync = verdadero
fuente
STDOUT.sync = true; system('<whatever-command>')
Blender probablemente no imprime saltos de línea hasta que finaliza el programa. En su lugar, está imprimiendo el carácter de retorno de carro (\ r). La solución más sencilla probablemente sea buscar la opción mágica que imprime saltos de línea con el indicador de progreso.
El problema es que
IO#gets
(y varios otros métodos de E / S) utilizan el salto de línea como delimitador. Leerán la transmisión hasta que lleguen al carácter "\ n" (que Blender no está enviando).Intente configurar el separador de entrada
$/ = "\r"
o utilice en sublender.gets("\r")
lugar.Por cierto, para problemas como estos, siempre debe marcar
puts someobj.inspect
op someobj
(ambos hacen lo mismo) para ver los caracteres ocultos dentro de la cadena.fuente
No sé si en ese momento ehsanul respondió la pregunta, ya estaba
Open3::pipeline_rw()
disponible, pero realmente simplifica las cosas.No entiendo el trabajo de ehsanul con Blender, así que hice otro ejemplo con
tar
yxz
.tar
agregará archivo (s) de entrada al flujo de salida estándar, luego loxz
tomarástdout
y lo comprimirá, nuevamente, en otra salida estándar . Nuestro trabajo es tomar la última salida estándar y escribirla en nuestro archivo 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
fuente
Pregunta antigua, pero tenía problemas similares.
Sin realmente cambiar mi código Ruby, una cosa que ayudó fue envolver mi tubería con stdbuf , así:
cmd = "stdbuf -oL -eL -i0 openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}" @xSess = IO.popen(cmd.split " ", mode = "w+")
En mi ejemplo, el comando real con el que quiero interactuar como si fuera un shell es openssl .
-oL -eL
dígale que almacene STDOUT y STDERR solo hasta una nueva línea. ReemplazarL
con0
para eliminar el búfer por completo.Sin embargo, esto no siempre funciona: a veces, el proceso de destino impone su propio tipo de búfer de flujo, como se señaló en otra respuesta.
fuente