Assignment to $stdout deprecated?

In pickaxe2, on p. 335, it says that assigning to $stdout is deprecated
and to use $stdout.reopen() instead:

-------------------------------------------------------------- IO#reopen
ios.reopen(other_IO) => ios
ios.reopen(path, mode_str) => ios

 Reassociates _ios_ with the I/O stream given in _other_IO_ or to a
 new stream opened on _path_. This may dynamically change the actual
 class of this stream.

    f1 = File.new("testfile")
    f2 = File.new("testfile")
    f2.readlines[0]   #=> "This is line one\n"
    f2.reopen(f1)     #=> #<File:testfile>
    f2.readlines[0]   #=> "This is line one\n"

And there is this in the Standard LIbrary:

StringIO

Once a string is wrapped in a StringIO object, it can be read from and
written to as if it were an open file…It also lets you pass strings
into classes and methods that were originally written to work with
files.

But I get an error trying to replace $stdout with a StringIO object:

require “stringio”

strio = StringIO.new
old_out = $stdout
$stdout.reopen(strio)

–output:–
r1test.rb:5:in `reopen’: cannot convert StringIO into String (TypeError)
from r1test.rb:5

What am I doing wrong?

I should add that assigning a StringIO object to $stdout does work:

require “stringio”

strio = StringIO.new
strio.write(“hello world”)

old_out = $stdout
$stdout = strio
puts " goodbye"
$stdout = old_out

strio.rewind()
puts strio.read()

–output:–
hello world goodbye

7stud – wrote:

-------------------------------------------------------------- IO#reopen
f1 = File.new(“testfile”)
f2 = File.new(“testfile”)
f2.readlines[0] #=> “This is line one\n”
f2.reopen(f1) #=> #<File:testfile>
f2.readlines[0] #=> “This is line one\n”

Also, what in the world is that example supposed to demonstrate?

2009/2/8 7stud – [email protected]:

old_out = $stdout
$stdout.reopen(strio)

–output:–
r1test.rb:5:in `reopen’: cannot convert StringIO into String (TypeError)
from r1test.rb:5

What am I doing wrong?

Not reading the docs.

It should be obvious from description of IO#reopen that you cannot use
StringIO in this way because it does not have an underlying file
descriptor.

On the other hand, you can assign a StrinIO into $stdout but this will
change only the global, not the file descriptors seen by C extensions
or programs executed with system().

HTH

Michal

Michal S. wrote:

What am I doing wrong?

Not reading the docs.

It should be obvious from description of IO#reopen that you cannot use
StringIO in this way because it does not have an underlying file
descriptor.

I posted the docs. They say no such thing.

7stud – wrote:

In pickaxe2, on p. 335, it says that assigning to $stdout is deprecated
and to use $stdout.reopen() instead:

It depends what you’re trying to do. I’ll describe this from a Unix
point of view.

Your Ruby program runs as a process which starts with three open file
descriptors: stdin (fd 0), stdout (fd 1), stderr (fd 2). These are
wrapped in Ruby objects STDIN, STDOUT, STDERR, and the global variables
$stdin, $stdout, $stderr point to these objects too.

If you do STDOUT.reopen(…) then you are closing fd 1 and replacing it
with a different file in the Unix file descriptor table. When your Ruby
program writes to STDOUT it will write to this file; but also any child
process spawned by your Ruby program will inherit this too (e.g. using
system() or backticks)

If you reassign $stdout to point to a completely different Ruby object,
then any code you write which does $stdout.puts will write to this
object - but FD 1 still remains connected to the original stdout.
Therefore, STDOUT.puts will still write to the original destination, as
will any child process.

$stdout = File.open("/tmp/stdout.txt",“w”)

puts “Hello” # uses $stdout, goes to the file
STDOUT.puts “World” # goes to the terminal (FD 1)
system(“echo Wheee”) # goes to the terminal (FD 1)

If you need to redirect to a StringIO object, then you have little
choice but to use $stdout, because a StringIO is not a Unix file, i.e.
it doesn’t have an entry in the file descriptor table.

(If you wanted to get very fancy, you could perhaps set up a pipe,
connect stdout to the writer end, and have a Ruby thread reading from
the reader end and appending to a StringIO object. But you’d only jump
through those hoops if you wanted the stdout from spawned child
processes to write to the StringIO too, and in that case you’d be better
off using IO.popen anyway)

Brian C. wrote:

Thanks for the detailed response.

If you reassign $stdout to point to a completely different Ruby object,
then any code you write which does $stdout.puts will write to this
object - but FD 1 still remains connected to the original stdout.

Ok.

Therefore, STDOUT.puts will still write to the original destination, as
will any child process.

Ok.

$stdout = File.open("/tmp/stdout.txt",“w”)

puts “Hello” # uses $stdout, goes to the file
STDOUT.puts “World” # goes to the terminal (FD 1)
system(“echo Wheee”) # goes to the terminal (FD 1)

AFAIK, all child processes receive a copy of their environment including
copies of all the variables in the parent. So echo doesn’t write to
$stdout because echo doesn’t understand ruby and therefore ignores
variables like $stdout?

If you need to redirect to a StringIO object, then you have little
choice but to use $stdout, because a StringIO is not a Unix file, i.e.
it doesn’t have an entry in the file descriptor table.

Ok.

(If you wanted to get very fancy, you could perhaps set up a pipe,
connect stdout to the writer end, and have a Ruby thread reading from
the reader end and appending to a StringIO object. But you’d only jump
through those hoops if you wanted the stdout from spawned child
processes to write to the StringIO too, and in that case you’d be better
off using IO.popen anyway)

Thanks for taking the time to write such an informative post.

2009/2/9 7stud – [email protected]:

I posted the docs. They say no such thing.
Yes, only IO::for_fd mentions the use of fds, IO#reopen does not
mention this limitation. This is probably a bug in the documentation.

Thanks

Michal

7stud – wrote:

AFAIK, all child processes receive a copy of their environment including
copies of all the variables in the parent.

Starting a different child process, such as a shell, involves fork()
followed by exec() in the child.

Immediately after the fork(), the child will have a copy of everything
the parent has, including the file descriptor table.

It then has the opportunity (if it wishes) to clean up the FD table
before calling exec(), and/or to pass a different environment or
arguments to the program which it is exec()ing.

So echo doesn’t write to
$stdout because echo doesn’t understand ruby and therefore ignores
variables like $stdout?

Basically, yes. echo just writes to FD 1. It doesn’t know anything about
Ruby or Ruby objects. Indeed since Ruby is running in a different
process, it cannot possibly access them (other than via some sort of
IPC, e.g. communicating over a pipe)

Regards,

Brian.