How do I dup file descriptors in ruby? (diverting STDERR)

Hello,

How would I do the equivalent to the following
Korn/Bourne code in ruby?

···

######################################

Save STDERR default

exec 3>&2

Send STDERR to a file

exec 2> logfile.text

Do stuff that might have error messages in it

Error will go to logfile.text instead of the

terminal.

/bin/ls non_existent_file
echo “hello world” 1>&2
run_non_existent_command

Restore STDERR

exec 2>&3

Close extra file descriptor

exec 3>&-

Error messages will now go to the terminal

######################################

Thank you for any help you can give,
Richard Ryan

“Richard A. Ryan” ryan@fsl.noaa.gov wrote in message news:3D5D7E39.7050601@fsl.noaa.gov

How would I do the equivalent to the following
Korn/Bourne code in ruby?

perhaps like this?

old_stderr = $stderr
begin
File.open( “logfile.text”, “a” ) { |log|
$stderr = logfile
#… do stuff
}
ensure
$stderr = old_stderr
end

~ Patrick

patrick-may@monmouth.com (Patrick May) writes:

“Richard A. Ryan” ryan@fsl.noaa.gov wrote in message news:3D5D7E39.7050601@fsl.noaa.gov

How would I do the equivalent to the following
Korn/Bourne code in ruby?

perhaps like this?

old_stderr = $stderr
begin
File.open( “logfile.text”, “a” ) { |log|
$stderr = logfile
#… do stuff
}
ensure
$stderr = old_stderr
end

~ Patrick

Thank you for the suggestion, but perhaps I should have been more
specific about what I really want to do. The following is closer to
what I’m interested in. It’s a very common lick to play in Korn, perl
and C and if I have to go through a symphony to do the same thing in
ruby then, to paraphrase the great American poet Chuck Berry, I’ll
lose the beauty of the melody.

···

########################################################################

For Korn, bash, zsh, POSIX shell and possibly maybe even Bourne

Save defaults for STDOUT AND STDERR

exec 3>&1
exec 4>&2

Send STDOUT and STDERR to a log file

exec 1> /tmp/logfile.out 2>&1

echo hello world
ls -l nonexistent_file
ls -l /etc/passwd
call_nonexistent_command

Restore STDOUT and STDERR

exec 1>&3
exec 2>&4

Close extra file descriptors

exec 3>&-
exec 4>&-

echo hello again world
ls -l nonexistent_file_again
ls -l /etc/group
call_nonexistent_command_again
########################################################################

Richard Ryan ryanr@12-253-103-174.client.attbi.com writes:

Thank you for the suggestion, but perhaps I should have been more
specific about what I really want to do. The following is closer to
what I’m interested in. It’s a very common lick to play in Korn,
perl and C and if I have to go through a symphony to do the same
thing in ruby then, to paraphrase the great American poet Chuck
Berry, I’ll lose the beauty of the melody.

Is this along the right lines?

 old_stdout = $stdout.clone
 old_stderr = $stderr.clone
 $stdout.reopen("stdout", "w")
 $stderr = $stdout
 puts "Hello world"
 system "ls -l a*"
 system "ls non_existent_file"
 $stdout = old_stdout
 $stderr = old_stderr
 puts "done"

Cheers

Dave

Thank you for the suggestion, but perhaps I should have
been more specific about what I really want to do. The
following is closer to what I’m interested in. It’s a
very common lick to play in Korn, perl and C and if I
have to go through a symphony to do the same thing in
ruby then, to paraphrase the great American poet Chuck
Berry, I’ll lose the beauty of the melody.

Cute analogy :slight_smile:

[… lick elided …]

Hmmm … I count 6 extra lines for your lick.

Fortunately, in Ruby you can capture licks in a JamMan effects box and
replay the lick whenever you like.

Write the following file one time and store it somewhere in your
standard Ruby path.

– File: redirect.rb -------------------------------------------------
def redirect_to(fn)
olddefout = $defout
oldstderr = $stderr
open(fn, “a”) do |file|
$defout = file
$stderr = file
begin
yield
ensure
$defout = olddefout
$stderr = oldstderr
end
end
end

···

Now, to playback your lick, just do …

require 'redirect'
redirect_to("logfile.txt") {
  puts "output now goes to the log file"
}

Just three lines of overhead instead of the six. And it handles
exceptions and early exits properly.


– Jim Weirich jweirich@one.net http://w3.one.net/~jweirich

“Beware of bugs in the above code; I have only proved it correct,
not tried it.” – Donald Knuth (in a memo to Peter van Emde Boas)

Dave Thomas Dave@PragmaticProgrammer.com writes:

Richard Ryan ryanr@12-253-103-174.client.attbi.com writes:

Thank you for the suggestion, but perhaps I should have been more
specific about what I really want to do. The following is closer to
what I’m interested in. It’s a very common lick to play in Korn,
perl and C and if I have to go through a symphony to do the same
thing in ruby then, to paraphrase the great American poet Chuck
Berry, I’ll lose the beauty of the melody.

Is this along the right lines?

 old_stdout = $stdout.clone
 old_stderr = $stderr.clone
 $stdout.reopen("stdout", "w")
 $stderr = $stdout
 puts "Hello world"
 system "ls -l a*"
 system "ls non_existent_file"
 $stdout = old_stdout
 $stderr = old_stderr
 puts "done"

Cheers

Dave

Hi,

Yes, those are right along the lines I was thinking, and that actually
was the first thing I tried but, and maybe I’m being too picky, if I
put that through irb and check $stderr.fileno I get something other
than 2. That really kind of bugs me for some reason. It just don’t
seem right.

Thanks again for any help you can give,
Richard Ryan

Jim Weirich jweirich@one.net writes:

> Thank you for the suggestion, but perhaps I should have
> been more specific about what I really want to do.  The
> following is closer to what I'm interested in.  It's a
> very common lick to play in Korn, perl and C and if I
> have to go through a symphony to do the same thing in
> ruby then, to paraphrase the great American poet Chuck
> Berry, I'll lose the beauty of the melody.

Cute analogy :slight_smile:

[… lick elided …]

Hmmm … I count 6 extra lines for your lick.

Fortunately, in Ruby you can capture licks in a JamMan effects box and
replay the lick whenever you like.

Write the following file one time and store it somewhere in your
standard Ruby path.

– File: redirect.rb -------------------------------------------------
def redirect_to(fn)
olddefout = $defout
oldstderr = $stderr
open(fn, “a”) do |file|
$defout = file
$stderr = file
begin
yield
ensure
$defout = olddefout
$stderr = oldstderr
end
end
end

Now, to playback your lick, just do …

require 'redirect'
redirect_to("logfile.txt") {
  puts "output now goes to the log file"
}

Just three lines of overhead instead of the six. And it handles
exceptions and early exits properly.


– Jim Weirich jweirich@one.net http://w3.one.net/~jweirich

“Beware of bugs in the above code; I have only proved it correct,
not tried it.” – Donald Knuth (in a memo to Peter van Emde Boas)

Hi,

Thanks for the suggestion. The problem with metaphor is that the
intended idea can be too easily miscommunicated. By ``symphony’'
I was referring to the complexity of the code structure.

Anyway, that being said, if you put the merging of stdout together
with stderr into the logfile which is in my second example, you’ll
probably get back at least the same number of lines, and you’ll still
have the complexity (if not quite a bit more). Also, please keep in
mind that I am thinking of putting a significant amount of code inside
of the redirection and merging—I would definitely like to avoid the
extra block if at all possible. Also, please note that in my second
example I ``un-merge’’ stdout from stderr when I’m done.

Thanks again,
Richard Ryan

Richard Ryan ryanr@12-253-103-174.client.attbi.com wrote in message news:m2sn1dkzox.fsf@12-253-103-174.client.attbi.com

Thanks for the suggestion. The problem with metaphor is that the
intended idea can be too easily miscommunicated. By ``symphony’'
I was referring to the complexity of the code structure.

If by complexity you mean not calling the same function 6 times with
different args, then by all means continue using your prefered tool:

exec 3>&1
exec 4>&2
exec 1> /tmp/logfile.out 2>&1

… do stuff

exec 1>&3
exec 2>&4
exec 3>&-
exec 4>&-

Anyway, that being said, if you put the merging of stdout together
with stderr into the logfile which is in my second example, you’ll
probably get back at least the same number of lines, and you’ll still
have the complexity (if not quite a bit more).

True, the simplist ruby example:

err = $stderr
out = $stdout
open( “logfile.text”, “a” ) do |file|
$stdout = $stderr = file
# … do stuff
end
$stderr = err
$stdout = out

However, if this is a common operation for you, Ruby allows you to
improve on the above by abstracting the functionality and letting you
pull it in. That’s what the require ‘redirect’ solution is about.

Also, please keep in
mind that I am thinking of putting a significant amount of code inside
of the redirection and merging—I would definitely like to avoid the
extra block if at all possible.

Which block is extra, and what is wrong with it?

Also, please note that in my second
example I ``un-merge’’ stdout from stderr when I’m done.

so have all the examples

~ Patrick

Yes, those are right along the lines I was thinking, and that actually
was the first thing I tried but, and maybe I'm being too picky, if I
put that through irb and check $stderr.fileno I get something other
than 2. That really kind of bugs me for some reason. It just don't
seem right.

This is because you must use $stderr.reopen, something like this

pigeon% cat b.rb
#!/usr/bin/ruby
def redirect(*args)
   defout = $defout
   stdout = $stdout.clone
   stderr = $stderr.clone
   $defout = File.new *args
   $defout.sync = true
   $stdout.reopen($defout)
   $stderr.reopen($defout)
   yield
ensure
   $defout.close
   $stdout.reopen(stdout)
   $stderr.reopen(stderr)
   $defout = defout
end

redirect('aaa', 'w') do
   puts "hello world"
   puts $stdout.fileno
   puts $stderr.fileno
   system("ls -l nonexistent_file")
   system("ls /etc/passwd")
end
puts "Hello World"
puts $stdout.fileno
puts $stderr.fileno
system("ls -l nonexistent_file")
system("ls /etc/group")
pigeon%

pigeon% b.rb
Hello World
1
2
ls: nonexistent_file: No such file or directory
/etc/group
pigeon%

pigeon% cat aaa
hello world
1
2
ls: nonexistent_file: No such file or directory
/etc/passwd
pigeon%

Guy Decoux

ts decoux@moulon.inra.fr writes:

Yes, those are right along the lines I was thinking, and that actually
was the first thing I tried but, and maybe I’m being too picky, if I
put that through irb and check $stderr.fileno I get something other
than 2. That really kind of bugs me for some reason. It just don’t
seem right.

This is because you must use $stderr.reopen, something like this

pigeon% cat b.rb
#!/usr/bin/ruby
def redirect(*args)
defout = $defout
stdout = $stdout.clone
stderr = $stderr.clone
$defout = File.new *args
$defout.sync = true
$stdout.reopen($defout)
$stderr.reopen($defout)
yield
ensure
$defout.close
$stdout.reopen(stdout)
$stderr.reopen(stderr)
$defout = defout
end

redirect(‘aaa’, ‘w’) do
puts "hello world"
puts $stdout.fileno
puts $stderr.fileno
system(“ls -l nonexistent_file”)
system(“ls /etc/passwd”)
end
puts "Hello World"
puts $stdout.fileno
puts $stderr.fileno
system(“ls -l nonexistent_file”)
system(“ls /etc/group”)
pigeon%

pigeon% b.rb
Hello World
1
2
ls: nonexistent_file: No such file or directory
/etc/group
pigeon%

pigeon% cat aaa
hello world
1
2
ls: nonexistent_file: No such file or directory
/etc/passwd
pigeon%

Guy Decoux

Hi all,

I guess I have to take back what I said about complexity. Although
this is laid out similarly to the code that was here before, there’s
something about how this brings all the other answers together that
shows me where my perceptions were perhaps a tad warped by naivete.
It takes awhile to adjust when you’re used to seeing things a certain
way—I used Korn as an example just because that’s where I’ve seen
this most commonly done and how I’ve most commonly used it myself.

Thanks to everyone who answered. This article here somehow
demonstrates to me what was going on with the other articles. Maybe
if there’s a ruby cookbook somewhere, maybe this would be good to have
in there. Does anyone else agree? FAQ maybe?

Thanks again,
Richard Ryan

ts decoux@moulon.inra.fr writes:

Yes, those are right along the lines I was thinking, and that actually
was the first thing I tried but, and maybe I’m being too picky, if I
put that through irb and check $stderr.fileno I get something other
than 2. That really kind of bugs me for some reason. It just don’t
seem right.

This is because you must use $stderr.reopen, something like this

pigeon% cat b.rb
#!/usr/bin/ruby
def redirect(*args)
defout = $defout
stdout = $stdout.clone
stderr = $stderr.clone
$defout = File.new *args
$defout.sync = true
$stdout.reopen($defout)
$stderr.reopen($defout)
yield
ensure
$defout.close
$stdout.reopen(stdout)
$stderr.reopen(stderr)
$defout = defout
end

redirect(‘aaa’, ‘w’) do
puts "hello world"
puts $stdout.fileno
puts $stderr.fileno
system(“ls -l nonexistent_file”)
system(“ls /etc/passwd”)
end
puts "Hello World"
puts $stdout.fileno
puts $stderr.fileno
system(“ls -l nonexistent_file”)
system(“ls /etc/group”)
pigeon%

pigeon% b.rb
Hello World
1
2
ls: nonexistent_file: No such file or directory
/etc/group
pigeon%

pigeon% cat aaa
hello world
1
2
ls: nonexistent_file: No such file or directory
/etc/passwd
pigeon%

Guy Decoux

Hi all,

I guess I have to take back what I said about complexity. Although
this is laid out similarly to the code that was here before, there’s
something about how this brings all the other answers together that
shows me where my perceptions were perhaps a tad warped by naivete.
It takes awhile to adjust when you’re used to seeing things a certain
way—I used Korn as an example just because that’s where I’ve seen
this most commonly done and how I’ve most commonly used it myself.

Thanks to everyone who answered. This article here somehow
demonstrates to me what was going on with the other articles. Maybe
if there’s a ruby cookbook somewhere, maybe this would be good to have
in there. Does anyone else agree? FAQ maybe?

Thanks again,
Richard Ryan

i’m not sure why, but modifying the constants STDOUT and STDERR seems
to give the ‘expected’ behavior both with the calling process and
withing sub-processes (backquoted sub-shell expressions). i tried all
manner of delegation, inheritance, extension - but the following is
simple and works like it reads :

···

===============================================================================
#!/usr/bin/env ruby

save everything for restoration

IN = STDIN.clone
ERR = STDERR.clone
OUT = STDOUT.clone

the log file

$log = open ‘logfile’, ‘w’

redirect STDOUT to logfile

STDOUT.reopen $log

merge STDERR and STDOUT

STDERR.reopen STDOUT

output goes to logfile

ls non_existent_file

output goes to logfile

$stderr.puts ‘foo’

output goes to logfile

$stdout.puts ‘bar’

output goes to logfile

puts ‘end test 0’

unmerge STDERR

STDERR.reopen ERR

output goes to stderr

ls non_existent_file

output goes to stderr

$stderr.puts ‘foo’

STDOUT still points to logfile…

output goes to logfile

$stdout.puts ‘bar’

output goes to logfile

puts ‘end test 1’

===============================================================================

i am still very confused as to why asigning to $stdout is so much
different that modifying STDOUT - especially when concerning
sub-processes…

-ara