【问题标题】:How do I temporarily redirect stderr in Ruby?如何在 Ruby 中临时重定向标准错误?
【发布时间】:2011-05-26 10:49:51
【问题描述】:

我想在一个块的持续时间内临时重定向 Ruby 脚本中的 stderr,确保我在块结束时将其重置为其原始值。

我在 ruby​​ 文档中找不到如何执行此操作。

【问题讨论】:

标签: ruby stderr


【解决方案1】:

在 Ruby 中,$stderr 指的是当前使用 的输出流作为标准错误,而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

【讨论】:

  • 我在一个函数 (RubyVM::InstructionSequence) 上使用它,该函数直接写入 C 中的 stderr(文件描述符 2)。这适用吗?我试图抑制 Ruby 运行时调用的低级 C 函数的标准错误输出
  • @banister:是的,它应该可以工作; Ruby 在内部使用$stderr。如果由于某种原因并非如此(这将是一个错误),那么不幸的是,您将无法在 Ruby 代码中对其进行任何处理。
  • 这在 ruby​​18 中不起作用。哪个 ruby​​ 有 StringIO 库?
  • 这很好,但在重新分配完成之前传递或复制$stderr 的值的情况下不起作用。相反,reopen 将适用于$stderr 的所有副本。问题是将StringIO 传递给reopen 不起作用,我收到此错误:“TypeError: no implicit conversion of StringIO into String”。
【解决方案2】:

这是一个更抽象的解决方案(感谢 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 }

【讨论】:

  • 我的 RUBY_PLATFORM == "i386-mingw32",所以这在我的 Windows 机器上不起作用。你永远不知道人们可能在运行什么奇怪的环境,所以我建议不要为此进行字符串匹配。
【解决方案3】:

与@molf 的回答基本相同,用法相同:

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

它使用 StringIO 更加简洁,并保留 $stderr 与调用 capture_stderr 之前一样。

【讨论】:

    【解决方案4】:

    我喜欢 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
    

    【讨论】:

    • 感谢您的解决方案!我基于它写了my own solution,添加文档和删除临时文件。
    猜你喜欢
    • 2011-10-11
    • 2010-12-29
    • 2016-10-06
    • 2021-03-14
    • 2012-03-30
    • 2020-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多