Open3で標準出力と標準エラー出力からの出力を全て読む

RubyにはOpen3というプロセスの起動とプロセス間通信用にIOポートの接続を行う便利なライブラリがある.

しかし,起動したプロセスの標準出力と標準エラー出力の両方を読もうとすると,デッドロックのような状態になってなかなか上手く行かない.

ググってみると,Threadを使うなどといった方法があるそうだが,とりあえず次のように書いてみた:

Open3.popen3(exe_name) {|stdin, stdout, stderr|
  stdin.puts(*input_to_stdin)
  stdin.close

  outid = stdout.fileno
  errid = stderr.fileno
  read_port = nil
  fileno_map_to_buff = {
    outid => external_out,
    errid => external_err,
  }
  fileno_map_port = {
    outid => stdout,
    errid => stderr,
  }

  while not fileno_map_port.empty?
    begin
      read_port_selected, = IO.select(fileno_map_port.values)
      read_port = read_port_selected[0]
      buff = read_port.readline
      fileno_map_to_buff[read_port.fileno] << buff
    rescue EOFError => e
      fileno_map_port.delete(read_port.fileno)
    end
  end
}

概要は,selectで各ポートが読み込み可能になるまで待ち,readlineでバッファを読む.EOFError例外を捕捉してselectで待つ必要が無くなったポートを削除する.各ポートと書き込み先の識別はIO#filenoで行っている,という感じだろうか.

特に例外を捕えて処理するのは微妙なところ…

もっと良い方法はないのかなぁ…

IOについては後でまた調べよう.

それと,このコードは結構適当(例外に頼るところとか)なので,もっと良い書き方に改良したい.