Odniosłem pewien sukces w rozwiązaniu tego mojego problemu. Oto szczegóły wraz z wyjaśnieniami, na wypadek gdyby ktoś miał podobny problem, znalazł tę stronę. Ale jeśli nie dbasz o szczegóły, oto krótka odpowiedź :
Używaj PTY.spawn w następujący sposób (oczywiście z własnym poleceniem):
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
A oto długa odpowiedź , ze zbyt wieloma szczegółami:
Prawdziwym problemem wydaje się być to, że jeśli proces nie opróżnia swojego standardowego wyjścia, to wszystko, co jest zapisywane na standardowe wyjście, jest buforowane, a nie wysyłane, dopóki proces nie zostanie zakończony, aby zminimalizować IO (jest to najwyraźniej szczegół implementacji wielu Biblioteki C, stworzone w taki sposób, że przepustowość jest maksymalizowana poprzez rzadsze IO). Jeśli możesz łatwo zmodyfikować proces tak, aby regularnie opróżniał standardowe wyjście, to byłoby to rozwiązanie. W moim przypadku był to blender, więc trochę onieśmielający dla kompletnego nooba, takiego jak ja, do modyfikowania źródła.
Ale kiedy uruchamiasz te procesy z powłoki, wyświetlają one standardowe wyjście do powłoki w czasie rzeczywistym, a standardowe wyjście nie wydaje się być buforowane. Są one buforowane tylko wtedy, gdy są wywoływane z innego procesu, jak sądzę, ale jeśli zajmujemy się powłoką, standardowe wyjście jest widoczne w czasie rzeczywistym, niebuforowane.
To zachowanie można nawet zaobserwować w przypadku procesu rubinowego jako procesu potomnego, którego dane wyjściowe muszą być gromadzone w czasie rzeczywistym. Po prostu utwórz skrypt, random.rb, z następującym wierszem:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Następnie skrypt ruby, aby go wywołać i zwrócić wynik:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Zobaczysz, że nie otrzymasz wyniku w czasie rzeczywistym, jak można by się spodziewać, ale wszystko naraz. STDOUT jest buforowany, nawet jeśli sam uruchomisz random.rb, nie jest buforowany. Można to rozwiązać, dodając STDOUT.flush
instrukcję wewnątrz bloku w random.rb. Ale jeśli nie możesz zmienić źródła, musisz to obejść. Nie można go przepłukać spoza procesu.
Jeśli podproces może drukować do powłoki w czasie rzeczywistym, musi istnieć sposób, aby przechwycić to również w Rubim w czasie rzeczywistym. I jest. Musisz użyć modułu PTY, zawartego w rdzeniu rubinowym, jak sądzę (w każdym razie 1.8.6). Smutne jest to, że nie jest to udokumentowane. Na szczęście znalazłem kilka przykładów użycia.
Po pierwsze, aby wyjaśnić, czym jest PTY, oznacza to pseudoterminal . Zasadniczo pozwala skryptowi ruby zaprezentować się podprocesowi tak, jakby był prawdziwym użytkownikiem, który właśnie wpisał polecenie w powłoce. Zatem każde zmienione zachowanie, które występuje tylko wtedy, gdy użytkownik uruchomił proces za pośrednictwem powłoki (na przykład STDOUT nie jest buforowane, w tym przypadku) wystąpi. Ukrywanie faktu, że inny proces rozpoczął ten proces, umożliwia gromadzenie STDOUT w czasie rzeczywistym, ponieważ nie jest on buforowany.
Aby to zadziałało ze skryptem random.rb jako dzieckiem, wypróbuj następujący kod:
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
to wszystko, czego potrzeba (odpowiedź mveermana poniżej). Oto kolejny wątek z przykładowym kodem .używać
IO.popen
. To jest dobry przykład.Twój kod wyglądałby tak:
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
źródło
yes
pomocą aplikacji wiersza poleceń, która nigdy się nie kończy i zadziałało. Kod był następujący:IO.popen('yes') { |p| p.each { |f| puts f } }
. Podejrzewam, że ma to związek z blenderem, a nie rubinem. Prawdopodobnie blender nie zawsze opróżnia swój STDOUT.STDOUT.flush lub STDOUT.sync = true
źródło
STDOUT.sync = true; system('<whatever-command>')
Blender prawdopodobnie nie wypisuje znaków końca linii, dopóki nie zakończy programu. Zamiast tego wypisuje znak powrotu karetki (\ r). Najłatwiejszym rozwiązaniem jest prawdopodobnie wyszukanie magicznej opcji, która wyświetla podziały wierszy ze wskaźnikiem postępu.
Problem polega na tym, że
IO#gets
(i wiele innych metod IO) używają podziału wiersza jako separatora. Będą czytać strumień, dopóki nie trafią w znak "\ n" (który blender nie wysyła).Spróbuj ustawić separator wejściowy
$/ = "\r"
lub użyjblender.gets("\r")
zamiast niego.Swoją drogą, w przypadku takich problemów należy zawsze sprawdzać
puts someobj.inspect
lubp someobj
(oba robią to samo), aby zobaczyć ukryte znaki w ciągu.źródło
Nie wiem, czy w tamtym czasie ehsanul odpowiedział na pytanie, było
Open3::pipeline_rw()
jeszcze dostępne, ale to naprawdę upraszcza sprawę.Nie rozumiem pracy ehsanula w Blenderze, więc zrobiłem kolejny przykład z
tar
ixz
.tar
doda plik (i) wejściowe do strumienia standardowego, a następniexz
weźmie gostdout
i skompresuje, ponownie, do innego wyjścia standardowego. Naszym zadaniem jest pobranie ostatniego standardowego wyjścia i zapisanie go w naszym ostatecznym pliku: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
źródło
Stare pytanie, ale miał podobne problemy.
Bez rzeczywistej zmiany mojego kodu Ruby, jedną rzeczą, która pomogła, było owinięcie mojego potoku stdbuf , na przykład:
cmd = "stdbuf -oL -eL -i0 openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}" @xSess = IO.popen(cmd.split " ", mode = "w+")
W moim przykładzie faktycznym poleceniem, z którym chcę wchodzić w interakcje tak, jakby była powłoką, jest openssl .
-oL -eL
powiedz mu, aby buforował STDOUT i STDERR tylko do nowej linii. WymienićL
z0
całkowicie unbuffer.To jednak nie zawsze działa: czasami proces docelowy wymusza własny typ bufora strumienia, jak wskazano w innej odpowiedzi.
źródło