web-dev-qa-db-ja.com

Rubyでstderrを一時的にリダイレクトするにはどうすればよいですか?

ブロックの期間中、Rubyスクリプト内のstderrを一時的にリダイレクトして、ブロックの最後でそれを元の値にリセットするようにしたいと思います。

Ruby docsでこれを行う方法を見つけるのに苦労しました。

43
horseyguy

Rubyでは、$stderr現在使用されているである出力ストリームをstderrとして参照しますが、STDERRdefault stderrストリームです。 $stderrに一時的に別の出力ストリームを割り当てるのは簡単です。

require "stringio"

def capture_stderr
  # The output stream must be an IO-like object. In this case we capture it in
  # an in-memory IO object so we can return the string value. You can assign any
  # IO object here.
  previous_stderr, $stderr = $stderr, StringIO.new
  yield
  $stderr.string
ensure
  # Restore the previous value of stderr (typically equal to STDERR).
  $stderr = previous_stderr
end

これで、次のことができるようになります。

captured_output = capture_stderr do
  # Does not output anything directly.
  $stderr.puts "test"
end

captured_output
#=> "test\n"

同じ原理が$stdoutSTDOUTでも機能します。

65
molf

これはより抽象的なソリューションです(クレジットはDavid Heinemeier Hanssonに提供されます):

def silence_streams(*streams)
  on_hold = streams.collect { |stream| stream.dup }
  streams.each do |stream|
    stream.reopen(Ruby_PLATFORM =~ /mswin/ ? 'NUL:' : '/dev/null')
    stream.sync = true
  end
  yield
ensure
  streams.each_with_index do |stream, i|
    stream.reopen(on_hold[i])
  end
end

使用法:

silence_streams(STDERR) { do_something }
17
user2398029

本質的に@molfの答えと同じで、同じ使い方があります:

require "stringio"
def capture_stderr
  real_stderr, $stderr = $stderr, StringIO.new
  yield
  $stderr.string
ensure
  $stderr = real_stderr
end

StringIOを非常にわずかに簡潔に使用し、$ stderrを、capture_stderrが呼び出される前の状態のまま保持します。

10
gunn

StringIOの回答が好きです。ただし、外部プロセスを呼び出している場合、$stderr = StringIO.newが機能しません。stderrを一時ファイルに書き出す場合があります。

require 'tempfile'

def capture_stderr
  backup_stderr = STDERR.dup
  begin
    Tempfile.open("captured_stderr") do |f|
      STDERR.reopen(f)
      yield
      f.rewind
      f.read
    end
  ensure
    STDERR.reopen backup_stderr
  end
end
6
zhon