Мне удалось решить эту мою проблему. Вот подробности с некоторыми пояснениями на тот случай, если кто-то, у кого есть подобная проблема, найдет эту страницу. Но если вас не интересуют подробности, вот краткий ответ :
Используйте 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
И вот длинный ответ со слишком большим количеством деталей:
Реальная проблема, похоже, заключается в том, что если процесс явно не сбрасывает свой стандартный вывод, то все, что написано в стандартный вывод, буферизуется, а не отправляется, пока процесс не будет завершен, чтобы минимизировать ввод-вывод (это, по- видимому, деталь реализации многих Библиотеки C, созданные таким образом, чтобы максимизировать пропускную способность за счет менее частого ввода-вывода). Если вы можете легко изменить процесс, чтобы он регулярно очищал стандартный вывод, то это было бы вашим решением. В моем случае это был блендер, поэтому для такого нуба, как я, было бы немного устрашать изменение исходного кода.
Но когда вы запускаете эти процессы из оболочки, они отображают stdout в оболочке в реальном времени, и stdout не кажется буферизированным. Я считаю, что он буферизуется только при вызове из другого процесса, но если обрабатывается оболочка, стандартный вывод отображается в реальном времени без буферизации.
Такое поведение можно наблюдать даже с процессом 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
STDOUT.sync = true
это все, что нужно (ответ Мвермана ниже). Вот еще один поток с примером кода .использовать
IO.popen
. Это хороший пример.Ваш код станет примерно таким:
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
источник
yes
помощью приложения командной строки, которое никогда не заканчивается , и оно сработало. Код был следующим:IO.popen('yes') { |p| p.each { |f| puts f } }
. Я подозреваю, что это связано с блендером, а не с рубином. Вероятно, блендер не всегда сбрасывает свой STDOUT.STDOUT.flush или STDOUT.sync = true
источник
STDOUT.sync = true; system('<whatever-command>')
Блендер, вероятно, не печатает разрывы строк, пока не завершит программу. Вместо этого он печатает символ возврата каретки (\ r). Самым простым решением, вероятно, является поиск волшебной опции, которая печатает разрывы строк с индикатором выполнения.
Проблема в том, что
IO#gets
(и другие различные методы ввода-вывода) используют разрыв строки в качестве разделителя. Они будут читать поток, пока не дойдут до символа «\ n» (который блендер не отправляет).Попробуйте установить разделитель ввода
$/ = "\r"
или использоватьblender.gets("\r")
вместо него.Кстати, для таких проблем вы всегда должны проверять
puts someobj.inspect
илиp someobj
(оба из которых делают одно и то же), чтобы увидеть любые скрытые символы в строке.источник
Я не знаю, отвечал ли на этот вопрос ehsanul, он еще был
Open3::pipeline_rw()
доступен, но это действительно упрощает работу.Я не понимаю, как ehsanul работает с Blender, поэтому я сделал другой пример с помощью
tar
иxz
.tar
добавит входной файл (ы) в поток stdout, затемxz
возьметstdout
его и снова сожмёт в другой stdout. Наша задача - взять последний стандартный вывод и записать его в наш окончательный файл: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
источник
Старый вопрос, но были похожие проблемы.
Без особого изменения моего кода Ruby, одна вещь, которая помогла, заключалась в том, чтобы обернуть мою трубу stdbuf , например:
cmd = "stdbuf -oL -eL -i0 openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}" @xSess = IO.popen(cmd.split " ", mode = "w+")
В моем примере фактическая команда, с которой я хочу взаимодействовать, как если бы это была оболочка, - это openssl .
-oL -eL
скажите ему буферизовать STDOUT и STDERR только до новой строки. ЗаменитьL
на,0
чтобы полностью отключить буфер.Однако это не всегда работает: иногда целевой процесс применяет собственный тип буфера потока, как указано в другом ответе.
источник