How do I temporarily redirect stderr in Ruby?

后端 未结 4 1351
暗喜
暗喜 2020-11-28 07:34

I\'d like to temporarily redirect stderr in a Ruby script for the duration of a block, ensuring that I reset it to its original value at the end of the block.

I had

相关标签:
4条回答
  • 2020-11-28 07:49

    In Ruby, $stderr refers to the output stream that is currently used as stderr, whereas STDERR is the default stderr stream. It is easy to temporarily assign a different output stream to $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
    

    Now you can do the following:

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

    The same principle also works for $stdout and STDOUT.

    0 讨论(0)
  • 2020-11-28 07:50

    Essentially the same as @molf's answer, and has the same usage:

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

    It uses StringIO very slightly more concisely, and preserves $stderr as whatever it was before capture_stderr was called.

    0 讨论(0)
  • 2020-11-28 08:06

    I like the StringIO answers. But if you are calling an external process, and $stderr = StringIO.new doesn't work, you might write stderr out to a temporary file:

    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
    
    0 讨论(0)
  • 2020-11-28 08:14

    Here is a more abstract solution (credit goes to 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
    

    Usage:

    silence_streams(STDERR) { do_something }
    
    0 讨论(0)
提交回复
热议问题