Saya telah berhasil memecahkan masalah saya ini. Berikut adalah detailnya, dengan beberapa penjelasan, jika ada orang yang memiliki masalah serupa menemukan halaman ini. Tetapi jika Anda tidak peduli dengan detailnya, inilah jawaban singkatnya :
Gunakan PTY.spawn dengan cara berikut (dengan perintah Anda sendiri tentunya):
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
Dan inilah jawaban panjangnya , dengan terlalu banyak detail:
Masalah sebenarnya tampaknya adalah bahwa jika suatu proses tidak secara eksplisit menghapus stdout-nya, maka apa pun yang ditulis ke stdout di-buffer daripada benar-benar dikirim, sampai proses selesai, untuk meminimalkan IO (ini tampaknya merupakan detail implementasi dari banyak Library C, dibuat agar throughput dimaksimalkan melalui IO yang lebih jarang). Jika Anda dapat dengan mudah memodifikasi prosesnya sehingga stdout secara teratur, maka itu akan menjadi solusi Anda. Dalam kasus saya, ini adalah blender, jadi agak mengintimidasi untuk noob lengkap seperti saya untuk memodifikasi sumbernya.
Tetapi ketika Anda menjalankan proses ini dari shell, mereka menampilkan stdout ke shell secara real-time, dan stdout tampaknya tidak di-buffer. Ini hanya buffer ketika dipanggil dari proses lain yang saya percaya, tetapi jika shell sedang ditangani, stdout terlihat secara real time, tanpa buffer.
Perilaku ini bahkan dapat diamati dengan proses ruby sebagai proses anak yang keluarannya harus dikumpulkan secara real time. Buat saja skrip, random.rb, dengan baris berikut:
5.times { |i| sleep( 3*rand ); puts "#{i}" }
Kemudian skrip ruby untuk memanggilnya dan mengembalikan outputnya:
IO.popen( "ruby random.rb") do |random|
random.each { |line| puts line }
end
Anda akan melihat bahwa Anda tidak mendapatkan hasil secara real-time seperti yang Anda harapkan, tetapi sekaligus setelahnya. STDOUT sedang di-buffer, meskipun jika Anda menjalankan random.rb sendiri, itu tidak di-buffer. Ini dapat diselesaikan dengan menambahkan STDOUT.flush
pernyataan di dalam blok secara random.rb. Tetapi jika Anda tidak dapat mengubah sumbernya, Anda harus mengatasinya. Anda tidak dapat membersihkannya dari luar proses.
Jika subproses dapat mencetak ke shell secara real-time, maka harus ada cara untuk menangkap ini dengan Ruby secara real-time juga. Dan ada. Anda harus menggunakan modul PTY, termasuk dalam ruby core yang saya percaya (1.8.6 lagian). Yang menyedihkan adalah itu tidak didokumentasikan. Tapi untungnya saya menemukan beberapa contoh penggunaan.
Pertama, untuk menjelaskan apa itu PTY adalah singkatan dari pseudo terminal . Pada dasarnya, ini memungkinkan skrip ruby untuk menampilkan dirinya sendiri ke subproses seolah-olah itu adalah pengguna sungguhan yang baru saja mengetik perintah ke dalam shell. Jadi setiap perubahan perilaku yang terjadi hanya ketika pengguna telah memulai proses melalui shell (seperti STDOUT tidak sedang buffer, dalam kasus ini) akan terjadi. Menyembunyikan fakta bahwa proses lain telah memulai proses ini memungkinkan Anda mengumpulkan STDOUT secara real-time, karena tidak sedang buffer.
Untuk membuat ini bekerja dengan skrip random.rb sebagai anak, coba kode berikut:
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
itulah yang dibutuhkan (jawaban mveerman di bawah). Berikut utas lain dengan beberapa kode contoh .gunakan
IO.popen
. Ini adalah contoh yang bagus.Kode Anda akan menjadi seperti ini:
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
sumber
yes
, aplikasi baris perintah yang tidak pernah berakhir , dan berhasil. Kode adalah sebagai berikut:IO.popen('yes') { |p| p.each { |f| puts f } }
. Saya curiga ini ada hubungannya dengan blender, dan bukan ruby. Mungkin blender tidak selalu membilas STDOUT-nya.STDOUT.flush atau STDOUT.sync = true
sumber
STDOUT.sync = true; system('<whatever-command>')
Blender mungkin tidak mencetak jeda baris sampai program berakhir. Sebagai gantinya, ini mencetak karakter carriage return (\ r). Solusi termudah mungkin mencari opsi ajaib yang mencetak jeda baris dengan indikator kemajuan.
Masalahnya adalah
IO#gets
(dan berbagai metode IO lainnya) menggunakan jeda baris sebagai pemisah. Mereka akan membaca aliran sampai mereka mencapai karakter "\ n" (yang tidak dikirim oleh blender).Coba atur pemisah input
$/ = "\r"
atau gunakanblender.gets("\r")
sebagai gantinya.BTW, untuk masalah seperti ini, Anda harus selalu memeriksa
puts someobj.inspect
ataup someobj
(keduanya melakukan hal yang sama) untuk melihat karakter tersembunyi di dalam string.sumber
Saya tidak tahu apakah pada saat itu ehsanul menjawab pertanyaan itu, masih
Open3::pipeline_rw()
ada, tapi itu sangat mempermudah.Saya tidak mengerti pekerjaan ehsanul dengan Blender, jadi saya membuat contoh lain dengan
tar
danxz
.tar
akan menambahkan file masukan ke aliran stdout, laluxz
mengambilnyastdout
dan memampatkannya, sekali lagi, ke stdout lain. Tugas kita adalah mengambil stdout terakhir dan menulisnya ke file terakhir kita: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
sumber
Pertanyaan lama, tapi punya masalah serupa.
Tanpa benar-benar mengubah kode Ruby saya, satu hal yang membantu adalah membungkus pipa saya dengan stdbuf , seperti:
cmd = "stdbuf -oL -eL -i0 openssl s_client -connect #{xAPI_ADDRESS}:#{xAPI_PORT}" @xSess = IO.popen(cmd.split " ", mode = "w+")
Dalam contoh saya, perintah sebenarnya yang ingin saya gunakan seolah-olah itu adalah shell, adalah openssl .
-oL -eL
katakan untuk buffer STDOUT dan STDERR hanya sampai baris baru. GantiL
dengan0
untuk melepas buffer sepenuhnya.Ini tidak selalu berhasil, meskipun: terkadang proses target memberlakukan jenis buffer alirannya sendiri, seperti jawaban lain yang ditunjukkan.
sumber