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