Does Ruby use $stdout for writing the output of puts and return?

Keyur Shah picture Keyur Shah · Jan 24, 2014 · Viewed 7.6k times · Source

I want to know the output stream being used by Ruby to print these things at the command-line:

irb(main):001:0> a="test"
=> "test"
irb(main):002:0> puts a
test
=> nil
irb(main):003:0> a
=> "test"

Is $stdout used for irb(main):002:0> and irb(main):003:0>? And, is there any change in the value of $stdout between those two invocations?

Also, can some one point me to the Ruby source from where these things get printed/written?

Answer

the Tin Man picture the Tin Man · Jan 25, 2014

Yes. And it's easy to test/prove to yourself. Try this at the command-line:

ruby -e 'puts "foo"' > test.out
cat test.out

The output will be:

foo

Ruby uses the STDOUT channel to output to the console. The OS then redirects that STDOUT to "test.out".

Try it with:

ruby -e 'STDOUT.puts "foo"' > test.out

and you'll get the same result.

If we do:

ruby -e 'STDERR.puts "foo"' > test.out
foo
cat test.out

You'll see nothing in the file, but "foo" will have been written to the console on the STDERR channel.

Ruby defines $stdout as a global you can change, and STDOUT as a constant, which you shouldn't change. Similarly, $stderr and STDERR are available.

Now, here's where it gets fun, and proves your question. Try this:

ruby -e '$stdout = STDERR; puts "foo"' > test.out

and you'll have the same results as when I output to STDERR, because, at puts was using the value for $stdout to select the output stream, and wrote to STDERR. Those stream values are picked up by Ruby from the OS when the interpreter starts, and are remembered during the run-time of the script. You can change them if necessary and Ruby will forget those settings when the interpreter exits, and reset itself to its normal state the next time.

You shouldn't rely on the implied/invisible behavior of changing $stdout though, because that leads to REALLY confusing code. Instead, I'd strongly recommend using an explicit STDERR.puts any time you're writing to STDERR and a bare puts for normal output to STDOUT. If you're intermingling output to both, then it'd probably be clearer to use STDOUT.puts and STDERR.puts, but that's your call.

Now, IRB is the same as a regular script running in the interpreter is, as far as using $stdout so writing output in IRB to $stdout works the same:

irb(main):001:0> $stdout
#<IO:<STDOUT>>
irb(main):002:0> $stderr
#<IO:<STDERR>>

And:

irb(main):003:0> $stdout.puts 'foo'
foo
nil
irb(main):004:0> $stderr.puts 'foo'
foo
nil

And finally:

irb(main):007:0> $stdout.isatty
true
irb(main):008:0> $stdout.isatty
true

We can't really tell any difference until we look a little lower; They're both TTY channels, with the standard STDOUT and STDERR channel numbers:

irb(main):009:0> $stdout.fileno
1
irb(main):010:0> $stderr.fileno
2

Hopefully that helps 'splain it.


I just realized that IRB's reporting of the return value of puts might be confusing you, causing you to think that the STDOUT is changing. That nil is returned has nothing to do with STDOUT or STDERR. It's because puts returns nil, which is dutifully reported by IRB.