Can't redirect STDOUT

9 views
Skip to first unread message

sc

unread,
Oct 14, 2006, 9:42:50 AM10/14/06
to
I'm now reading "Network Programming w/ Perl". And I modified an
example, but can not get the same result.

The example is as following, excepted from "Network Programming w/
Perl":

#!/usr/bin/perl
# file: redirect.pl

print "Redirecting STDOUT\n";
open (SAVEOUT,">&STDOUT");
open (STDOUT,">test.txt") or die "Can't open test.txt: $!";

print "STDOUT is redirected\n";
system "date";

open (STDOUT,">&SAVEOUT");
print "STDOUT restored\n";

After running, the contents of test.txt are:

STDOUT is redirected
Sat Oct 14 21:17:30 CST 2006

Then I try to use local to get the same result, but failed. Here's my
program:

#!/usr/bin/perl

print "Redirecting STDOUT\n";
{
local *STDOUT;
open STDOUT, ">test.txt";

print "STDOUT is redirected\n";
system "date";
print "After call system()\n";
}
print "STDOUT restored\n";

After running, the output of system "date" just goto the screen! And
the contents of test.txt are:

STDOUT is redirected
After call system()

It's weird! Why the output of "system" can't goto redirected STDOUT?

TIA

-sc

Brian McCauley

unread,
Oct 14, 2006, 12:50:15 PM10/14/06
to

On Oct 14, 2:42 pm, "sc" <s...@rimple.net> wrote:
> I'm now reading "Network Programming w/ Perl". And I modified an
> example, but can not get the same result.
>
> The example is as following, excepted from "Network Programming w/
> Perl":
>
> #!/usr/bin/perl
> # file: redirect.pl
>
> print "Redirecting STDOUT\n";
> open (SAVEOUT,">&STDOUT");

In modern Perl this is better written:

open ( my $saveout,'>&', *STDOUT) or die $!;

> open (STDOUT,">test.txt") or die "Can't open test.txt: $!";
>
> print "STDOUT is redirected\n";
> system "date";
>
> open (STDOUT,">&SAVEOUT");

open (STDOUT,'>&', $saveout) or die $!;

> print "STDOUT restored\n";
>
> After running, the contents of test.txt are:
>
> STDOUT is redirected
> Sat Oct 14 21:17:30 CST 2006
>
> Then I try to use local to get the same result, but failed. Here's my
> program:
>
> #!/usr/bin/perl
>
> print "Redirecting STDOUT\n";
> {
> local *STDOUT;
> open STDOUT, ">test.txt";
>
> print "STDOUT is redirected\n";
> system "date";
> print "After call system()\n";}print "STDOUT restored\n";
>
> After running, the output of system "date" just goto the screen! And
> the contents of test.txt are:
>
> STDOUT is redirected
> After call system()
>
> It's weird! Why the output of "system" can't goto redirected STDOUT?

The simple answer is because you added local *STDOUT.

The more complex answer is because there are many protocaol layers
involved in IO.

Some of these layers are in user-space and some are in kernel space.
Inside kernel space there are filehandle-like enties often called file
control blocks or some such. At the user/kernel interface there are
"file descriptors" which are non-negative integers.

Various operating system calls can be used to control the FCBs and
their associated FDs. For example dup() will associate a previously
unassociated FD with an existing FCB. open() will create a new FCB.
When a precess fork()s (or in some OSs when a process is spawned) the
new process can inherit FDs. What this means is not that the FDs are
actually shared (although this is a shorthand terminonolgy that's used)
but that the FD in the child refers to the same FCB as the parent.

The filedescriptors 0 1 and 2 have special conventional meaings
"standard in", "standard out", "standard err". When a process wants to
generate "standard output" it sends output to FD 1. And unless it's
re-open()ed FD 1 or close()d FD 1 dup2()ed FD 1 (or whatever) this will
go via the FCB that was associated with the parent's FD 1.

Now in user-space the higher level language introduces another layer of
buffering and suchlike. In Perl with have fileandles (also known as
IO-thingys). When a Perl process starts the IO-thingys identified by
*STDOUT{IO} is bound to FD 1.

When you issue open(STDOUT,...) in Perl the associated IO-thingy will
try to carry on using FD1

But if you do local(*STDOUT) you stash away the current contents of
*STDOUT{IO} and make it empty. Now when you open(STDOUT,...) you get a
new IO-thingy associated with *STDOUT{IO} but this IO-thingy is not
associated with FD 1. As far as the current Perl process is concerned
this is now the "standard output" but any child process that's created
will still think of FD 1 as standard out.

On the other hand when I say...

open ( my $saveout,'>&', *STDOUT) or die $!;

...Perl creates a new pseudo-anonymous GLOB and associuates the IO slot
of that GLOB a new IO-thingy and associates that IO-thingy with the FD
it gets from dup()ing FD 1. So in effect $saveout now retains a
pointer (of sorts) to the FCB that was your original standard output.

And when I say...

open (STDOUT,'>&', $saveout) or die $!;

...this reassociates FD 1 with the original FCB.

Of course you may want to make sure this happens on all execution paths
(which I assume is why you wanted to use local()).

use AtExit;
open ( my $saveout,'>&', *STDOUT) or die $!;
my $restoreout = AtExit->new( sub {
# Actually the or die is of dubious utility here
open (STDOUT,'>&', $saveout) or die $!;
});

Peter Scott

unread,
Oct 15, 2006, 8:52:43 AM10/15/06
to
On Sat, 14 Oct 2006 09:50:15 -0700, Brian McCauley wrote:
[snip amazingly clear and concise explanation]

> But if you do local(*STDOUT) you stash away the current contents of
> *STDOUT{IO} and make it empty. Now when you open(STDOUT,...) you get a
> new IO-thingy associated with *STDOUT{IO} but this IO-thingy is not
> associated with FD 1. As far as the current Perl process is concerned
> this is now the "standard output" but any child process that's created
> will still think of FD 1 as standard out.

Although it's clear in the context of the post you were replying to, since
your posting is worth saving for posterity and repeating, it's worth
clarifying that "child process" there refers to a program that's been
exec()ed in some way, not a fork()ed perl process that'll still be using
the copied IO-thingy:

$ rm /tmp/stdout; perl -le 'local *STDOUT; \
open STDOUT, ">/tmp/stdout"; print "parent"; \
exit if fork; print "perl child"; \
exec "echo shell call"'
shell call
$ cat /tmp/stdout
parent
perl child

--
Peter Scott
http://www.perlmedic.com/
http://www.perldebugged.com/

Reply all
Reply to author
Forward
0 new messages