Tive algum sucesso em resolver este meu problema. Aqui estão os detalhes, com algumas explicações, caso alguém com problema semelhante encontre esta página. Mas se você não se importa com os detalhes, aqui está a resposta curta :
Use PTY.spawn da seguinte maneira (com seu próprio comando, é claro):
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
E aqui está a resposta longa , com muitos detalhes:
O verdadeiro problema parece ser que se um processo não liberar explicitamente seu stdout, então qualquer coisa gravada em stdout é armazenada em buffer em vez de realmente enviada, até que o processo seja concluído, para minimizar IO (este é aparentemente um detalhe de implementação de muitos Bibliotecas C, feitas de forma que o rendimento seja maximizado por meio de E / S menos frequente). Se você puder modificar facilmente o processo para que ele libere o stdout regularmente, essa seria sua solução. No meu caso, era o liquidificador, então um pouco intimidante para um noob completo como eu modificar a fonte.
Mas quando você executa esses processos a partir do shell, eles exibem stdout no shell em tempo real, e o stdout não parece estar em buffer. Ele só é armazenado em buffer quando chamado de outro processo, eu acredito, mas se um shell está sendo tratado, o stdout é visto em tempo real, sem buffer.
Esse comportamento pode até ser observado com um processo ruby como o processo filho cuja saída deve ser coletada em tempo real. Basta criar um script, random.rb, com a seguinte linha:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Em seguida, um script ruby para chamá-lo e retornar sua saída:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Você verá que não obtém o resultado em tempo real como poderia esperar, mas tudo de uma vez depois. STDOUT está sendo armazenado em buffer, embora se você executar random.rb por conta própria, ele não será armazenado em buffer. Isso pode ser resolvido adicionando uma STDOUT.flush
instrução dentro do bloco em random.rb. Mas se você não pode mudar a fonte, você tem que contornar isso. Você não pode liberá-lo de fora do processo.
Se o subprocesso pode imprimir no shell em tempo real, então deve haver uma maneira de capturar isso com Ruby também em tempo real. E aqui está. Você tem que usar o módulo PTY, incluído no ruby core, eu acredito (1.8.6 de qualquer maneira). O triste é que não está documentado. Mas eu encontrei alguns exemplos de uso felizmente.
Primeiro, para explicar o que é PTY, significa pseudo terminal . Basicamente, ele permite que o script ruby se apresente ao subprocesso como se fosse um usuário real que acabou de digitar o comando em um shell. Portanto, qualquer comportamento alterado que ocorra apenas quando um usuário iniciar o processo por meio de um shell (como o STDOUT não sendo armazenado em buffer, neste caso) ocorrerá. Esconder o fato de que outro processo foi iniciado permite que você colete o STDOUT em tempo real, pois ele não está sendo armazenado em buffer.
Para fazer isso funcionar com o script random.rb como filho, tente o seguinte código:
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
é tudo o que é necessário (resposta de Mveerman abaixo). Aqui está outro tópico com algum código de exemplo .usar
IO.popen
. Este é um bom exemplo.Seu código se tornaria algo como:
fonte
yes
um aplicativo de linha de comando que nunca termina e funcionou. O código era a seguinte:IO.popen('yes') { |p| p.each { |f| puts f } }
. Suspeito que seja algo relacionado ao liquidificador e não ao rubi. Provavelmente o liquidificador nem sempre libera seu STDOUT.STDOUT.flush ou STDOUT.sync = true
fonte
STDOUT.sync = true; system('<whatever-command>')
O Blender provavelmente não imprime quebras de linha até terminar o programa. Em vez disso, está imprimindo o caractere de retorno de carro (\ r). A solução mais fácil é provavelmente procurar a opção mágica que imprime quebras de linha com o indicador de progresso.
O problema é que
IO#gets
(e vários outros métodos de ES) usam a quebra de linha como um delimitador. Eles lerão o stream até atingirem o caractere "\ n" (que o blender não está enviando).Tente definir o separador de entrada
$/ = "\r"
ou usarblender.gets("\r")
.BTW, para problemas como esses, você deve sempre verificar
puts someobj.inspect
oup someobj
(ambos fazem a mesma coisa) para ver quaisquer caracteres ocultos dentro da string.fonte
Não sei se no momento em que o ehsanul respondeu à pergunta, ela ainda estava
Open3::pipeline_rw()
disponível, mas realmente torna as coisas mais simples.Não entendo o trabalho do ehsanul com o Blender, então fiz outro exemplo com
tar
exz
.tar
irá adicionar arquivo (s) de entrada ao fluxo stdout, entãoxz
pegar issostdout
e compactá-lo, novamente, para outro stdout. Nosso trabalho é pegar o último stdout e gravá-lo em nosso arquivo final:fonte
Pergunta antiga, mas tinha problemas semelhantes.
Sem realmente mudar meu código Ruby, uma coisa que ajudou foi envolver meu pipe com stdbuf , assim:
No meu exemplo, o comando real com o qual desejo interagir como se fosse um shell é o openssl .
-oL -eL
diga a ele para armazenar STDOUT e STDERR somente até uma nova linha. SubstituaL
por0
para desempacotar completamente.Porém, isso nem sempre funciona: às vezes o processo de destino impõe seu próprio tipo de buffer de fluxo, como outra resposta apontada.
fonte