2011-08-19 14 views
7

Pracuję nad skryptem, który używa IO.popen do otwarcia innego programu i ciągłego odczytu danych. To jest tak:Ruby IO.popen STDOUT buforowanie

process = IO.popen(["/the/program", "argument", "argument"]) 

loop do 
    line = process.gets 
    puts "#{line}" 
end 

(Rzeczywisty program robi więcej niż tylko drukowanie wyjścia, oczywiście - to tylko przykład).

Kwestia biegnę na to, że popen wydaje się być buforowanie STDOUT z otwartego procesu. Potwierdziłem to, uruchamiając program bezpośrednio z powłoki i poprzez popen, side-by-side, a Ruby nigdy nie dostaje jednej linii na raz. Zawsze pobiera wiele linii naraz i jest opóźniony.

Próbowałem

STDOUT.sync = true 

... przed popen, ale to niczego nie zmieniło.

Ten program zdecydowanie używa \ n jako nowej linii, więc to nie jest problem.

+0

Czy wypróbowałeś 'process.sync = true'? – DNNX

+0

@Eddie to wymyśliłeś? –

Odpowiedz

5

Czy masz źródło do innego programu? Musisz wymusić na innym programie wypróżnienie jego wyjścia lub sprawić, by twój skrypt wyglądał jak pty (zobacz standardową bibliotekę pty).

Zapoznaj się z this question, aby uzyskać dobre wyjaśnienie, co się dzieje.

EDIT: PTY przykładowy kod:

require 'pty' 
PTY.spawn "some-command" do |r,w,p| 
    loop { puts r.gets } 
end 
+0

Nie można wydrukować ostatniego wiersza, jeśli nie kończy się on znakiem nowej linii (np. Jeśli jakieś polecenie to echo -n ABC). –

3

Podejrzewam /the/program jest buforowanie po wykryciu stdout nie jest terminal - można testować przez rurociągów przez kota, np:

"/the/program" "argument" "argument" | cat 

Powyższa odpowiedź rozwiąże problem, jeśli jest to problem, tj .:

#!/usr/bin/env ruby 

require 'pty' 
PTY.spawn "./the-program testing one Two three" do |r,w,p| 
    loop { puts "GOT: #{r.gets}" } 
end 

Niektóre języki (np c) wykrywania, czy stdout jest terminal i zmienić na linię buforowane - patrz Is stdout line buffered, unbuffered or indeterminate by default?

Jako przykład kiedy działa, użyłem prostego skryptu bash do wyjścia każdy argument i czas, jeden po drugim czas, z 3 sekundami pomiędzy i skryptem ruby ​​działał bez problemu. Dodałem wykrywanie eof dla tego przykładu.

Zmodyfikowany skrypt:

zawartość
#!/usr/bin/env ruby 

process = IO.popen(["./the-program", "testing", "one", "Two", "three"]) 

while !process.eof? 
    line = process.gets 
    puts "GOT: #{line}" 
end 

the-programowe:

#!/bin/bash 

for arg 
do 
    echo $arg 
    date 
    sleep 3 
done 

Próbowałem z rubinowym wersji 1.9.3 oraz 2.1.2

$ ruby ,p 
GOT: testing 
GOT: Mon Jun 16 06:19:00 EST 2014 
GOT: one 
GOT: Mon Jun 16 06:19:03 EST 2014 
GOT: Two 
GOT: Mon Jun 16 06:19:06 EST 2014 
GOT: three 
GOT: Mon Jun 16 06:19:09 EST 2014 
$ 

Jeśli używam C zamiast tego program ponownie się uruchamia:

#include <stdio.h> 

main(int argc, char **argv) 
{ 
     int i; 

     for (i=0; i<argc; i++) { 
       printf("%s\n", argv[i]); 
       sleep(3); 
     } 
}