2

I would like to capture stderr into a variable in memory, without using a file on the filesystem. (This is because if the process is kill -9'ed, the file, even if it is a Tempfile, will not be deleted.)

There is a solution for this at How do I temporarily redirect stderr in Ruby?, but its strategy is to assign a StringIO to $stderr. This will not work if the value of $stderr was copied prior to the reassignment, nor will it work if STDERR is used. As evidence:

#!/usr/bin/env ruby

stderr_sav = $stderr
$stderr = File.open(File::NULL, 'w')
$stderr.puts    'Using $stderr'
stderr_sav.puts 'Using stderr_sav'
STDERR.puts     'Using STDERR'

# Outputs:

# Using stderr_sav
# Using STDERR

In contrast, using reopen works:

#!/usr/bin/env ruby

stderr_sav = $stderr
$stderr.reopen(File.new(File::NULL, 'w'))
$stderr.puts    'Using $stderr'
stderr_sav.puts 'Using stderr_sav'
STDERR.puts     'Using STDERR'

# Outputs:

# [nothing]

Unfortunately, passing a StringIO to reopen does not work:

`reopen': no implicit conversion of StringIO into String (TypeError)

Is there any way to accomplish the capturing of $stderr without using a file?

Keith Bennett
  • 4,722
  • 1
  • 25
  • 35

1 Answers1

0

How about a proxy which temporary delegate IO#puts to StringIO#puts

stderr_sav = $stderr

# delegate
$stdclone = $stderr.clone
$temp = StringIO.new

[:puts].each do |m|
  if $temp.respond_to?(m)
    $stderr.define_singleton_method "#{m}" do |*args, &block|
      $temp.send(m, *args, &block)
    end
  end
end

# test
$stderr.puts 'secret1' # nothing
STDERR.puts 'secret2' # nothing
stderr_sav.puts 'secret3' # nothing

new_strerr = IO.new(1)
new_strerr.puts "not secret" # not secret

$stdclone.puts $temp.string # secret 1 -> 3

# reset
$stderr = $stdclone
$stderr.puts "secret4" # secret4
Lam Phan
  • 3,405
  • 2
  • 9
  • 20
  • That's an interesting approach. As I mentioned in the question, assignment will not be sufficient for me, but the idea of substituting a method might work. I could save the method object, replace it temporarily, then restore it. There may be several methods in addition to `puts` that need to be modified. Perhaps there is a single low level write method that would be the only one needed. – Keith Bennett May 07 '21 at 00:34
  • 1
    @KeithBennett you can delegate list of methods as i've just updated on my answer. – Lam Phan May 07 '21 at 04:39
  • Thanks, that's getting closer to what I need. When restoring to the original behavior, though, I cannot use assignment; there is the risk that the modified $stderr was saved somewhere and retain the behavior change that was supposed to be temporary. We'd need to find way to save the original methods and restore them to $stderr, without changing the object that $stderr points to. – Keith Bennett May 07 '21 at 21:47