Я мав певний успіх у вирішенні цієї своєї проблеми. Ось деталі з деякими поясненнями, якщо хтось із подібною проблемою знайде цю сторінку. Але якщо ви не дбаєте про деталі, ось коротка відповідь :
Використовуйте 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
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 бачиться в режимі реального часу без буфера.
Цю поведінку можна спостерігати навіть при рубіновому процесі як дочірньому процесі, вихід якого повинен збиратися в режимі реального часу. Просто створіть скрипт random.rb із таким рядком:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Потім рубіновий скрипт для його виклику і повернення результату:
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, це означає псевдотермінал . По суті, це дозволяє рубіновому скрипту представити себе в підпроцесі так, ніби це справжній користувач, який щойно ввів команду в оболонку. Отже, відбуватиметься будь-яка змінена поведінка, яка виникає лише тоді, коли користувач запускає процес через оболонку (наприклад, 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