У меня был некоторый успех в решении этой моей проблемы. Вот подробности, с некоторыми пояснениями, на случай, если кто-то с подобной проблемой найдет эту страницу. Но если вам не нужны подробности, вот краткий ответ :
Используйте PTY.spawn следующим образом (с вашей собственной командой, конечно):
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
И вот длинный ответ со слишком большим количеством деталей:
Реальная проблема заключается в том, что если процесс явно не сбрасывает свой стандартный вывод, то все, что записано в стандартный вывод, буферизуется, а не фактически отправляется, до тех пор, пока процесс не будет выполнен, чтобы минимизировать IO (это очевидно, деталь реализации многих библиотек C, сделанная так, чтобы пропускная способность была увеличена за счет менее частого ввода-вывода). Если вы можете легко изменить процесс так, чтобы он регулярно сбрасывал стандартный вывод, это было бы вашим решением. В моем случае это был блендер, поэтому немного пугающим для такого нуба, как я, было изменение источника.
Но когда вы запускаете эти процессы из оболочки, они отображают стандартный вывод в оболочку в режиме реального времени, и стандартный вывод, похоже, не буферизуется. Я полагаю, что он буферизуется только при вызове из другого процесса, но при работе с оболочкой стандартный вывод отображается в реальном времени без буферизации.
Такое поведение можно даже наблюдать с помощью процесса ruby как дочернего процесса, выходные данные которого должны собираться в режиме реального времени. Просто создайте скрипт random.rb со следующей строкой:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Затем скрипт ruby для его вызова и возврата:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Вы увидите, что вы не получите результат в режиме реального времени, как вы ожидаете, но сразу после этого. STDOUT буферизируется, даже если вы запускаете random.rb самостоятельно, он не буферизируется. Это можно решить, добавив оператор STDOUT.flush
внутри блока в random.rb. Но если вы не можете изменить источник, вы должны обойти это. Вы не можете очистить его извне процесса.
Если подпроцесс может печатать в оболочку в режиме реального времени, то должен быть способ зафиксировать это и в Ruby в режиме реального времени. И есть. Вы должны использовать модуль PTY, включенный в ядро ruby, как мне кажется (1.8.6 в любом случае). Печально то, что это не задокументировано. Но, к счастью, я нашел несколько примеров использования.
Во-первых, чтобы объяснить, что такое PTY, оно обозначает псевдотерминал . По сути, он позволяет сценарию ruby представлять себя подпроцессу, как будто это настоящий пользователь, который только что ввел команду в оболочку. Таким образом, произойдет любое измененное поведение, которое происходит только тогда, когда пользователь запустил процесс через оболочку (например, STDOUT в этом случае не буферизуется). Сокрытие того факта, что этот процесс запущен другим процессом, позволяет собирать STDOUT в режиме реального времени, поскольку он не буферизируется.
Чтобы это работало со скриптом random.rb в качестве дочернего, попробуйте следующий код:
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