Непрерывно читайте из STDOUT внешнего процесса в Ruby

Для расширения немного на том, что говорит Brad: много транспортных механизмов для электронной почты и Usenet и других способов переместить данные не "8 битов, чистых", что означает, что символы вне стандартного набора символов ASCII могли бы быть искажены в пути - например, 0x0D мог бы рассматриваться как возврат каретки и превращаться возврат каретки и перевод строки. Основывайте 64 карты все двоичные символы в несколько стандартных букв ASCII и чисел и пунктуации, таким образом, они не будут искажены этот путь.

84
задан ehsanul 20 July 2009 в 18:04
поделиться

4 ответа

Мне удалось решить эту мою проблему. Вот подробности с некоторыми пояснениями на тот случай, если кто-то, у кого возникнет аналогичная проблема, найдет эту страницу. Но если вас не интересуют подробности, вот краткий ответ :

Используйте 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

И вот длинный ответ со слишком большим количеством деталей:

Реальная проблема, похоже, в том, что если процесс явно не очищает свой стандартный вывод, то все, что записано в стандартный вывод, буферизуется, а не отправляется, пока процесс не будет завершен , чтобы минимизировать ввод-вывод (это , по-видимому, деталь реализации многих библиотек C, сделанная так, что пропускная способность максимизируется за счет менее частого ввода-вывода). Если вы можете легко изменить процесс, чтобы он регулярно сбрасывал 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 буферизуется, даже если вы запускаете его в случайном порядке. 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
173
ответ дан 24 November 2019 в 08:31
поделиться

используйте 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
12
ответ дан 24 November 2019 в 08:31
поделиться

Блендер, вероятно, не печатает разрывы строк, пока это завершение программы. Вместо этого он печатает символ возврата каретки (\ r). Самым простым решением, вероятно, является поиск волшебной опции, которая печатает разрывы строк с индикатором выполнения.

Проблема в том, что IO # получает (и различные другие методы IO) используют разрыв строки в качестве разделителя . Они будут читать поток, пока не дойдут до символа "\ n" (который не отправляет блендер).

Попробуйте установить разделитель ввода $ / = "\ r" или используйте блендер .gets ("\ r") вместо этого.

Кстати, для подобных проблем

4
ответ дан 24 November 2019 в 08:31
поделиться

STDOUT.flush или STDOUT.sync = true

5
ответ дан 24 November 2019 в 08:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: