rubystdoutstderrbufferingoutput-redirect

Problem redirecting stdout in Ruby script


I have the following test Ruby script:

require 'tempfile'

tempfile = Tempfile.new 'test'
$stderr.reopen tempfile
$stdout.reopen tempfile

puts 'test stdout'
warn 'test stderr'
`mail -s 'test' my@email.com < #{tempfile.path}`

tempfile.close
tempfile.unlink
$stderr.reopen STDERR
$stdout.reopen STDOUT

The email that I get has the contents:

test stderr

Why is stderr redirecting properly but not stdout?

Edit: In response to a comment I added a $stdout.flush after the puts line and it printed correctly. So I'll restate my question: what was happening and why does the flush fix it?


Solution

  • The standard output is generally buffered to avoid a system call for every write. So, when you say this:

    puts 'test stdout'
    

    You're actually just stuffing that string into the buffer. Then you say this:

    `mail -s 'test' my@email.com < #{tempfile.path}`
    

    and your 'test stdout' string is still in the buffer so it isn't in tempfile when mail sends the file's content to you. Flushing $stdout forces everything in the buffer to be written to disk; from the fine manual:

    Flushes any buffered data within ios to the underlying operating system (note that this is Ruby internal buffering only; the OS may buffer the data as well).

    $stdout.print "no newline"
    $stdout.flush
    

    produces:

    no newline
    

    The standard error is often unbuffered so that error messages (which are supposed to be rare) are visible immediately.