tags:

views:

904

answers:

6

When I run this:

open FP, ">xyz";

my $file = *FP;

printf $file "first\n";

$file = *STDOUT;

printf $file "second\n";

open $file, ">abc";

print $file "third\n";

print STDOUT "fourth\n";

print FP "fifth\n";

The "fourth" print does not go to STDOUT, rather to "abc".

STDOUT is different from FP which behaves as expected.

What am I doing wrong? What am I not understanding?

+1  A: 

When you do open $file, ">abc", what you're fundamentally operating on is the file descriptor for STDOUT, which you've now reopened to a different target. That is, both of your aliases operate on a single underlying resource which is being repurposed, irrespective of the alias used to refer to it, when you do that open.

chaos
+11  A: 

Well, for starters, you're using 'open' wrongly.

open my $fp , '>', 'xyz' ;

Is the recommended syntax.

the bare 'FP' you have there is strongly recommended against, as it is not a lexical.

Secondly, you're re-opening file-pointers as new things. This is not very good practice, it shouldn't be a problem, but its just a bad idea. You should close the file pointer or let it run out of scope ( via a lexical ).

Thirdly, '*STDOUT' is a reference.

my $fh = *STDOUT; 
print "$fh\n";   #prints  '*main::STDOUT';

So when you do this:

open $fh, '>abc';

you're doing

open *STDOUT, '>abc';

and if you immediately afterwards do

print "$fh\n";

you will note it still prints *main::STDOUT;

Some interesting code snippets that clear this up: my $fh = *STDOUT;

open $fh, '<', "foo.txt"; 
print $fh "hello";
# Filehandle STDOUT opened only for input at (eval 288) line 6.


my $fh = *STDIN;
open $fh, '<', "foo.txt"; 
print <>; 
# contents of foo.txt here

Here's a recommended way to use open:

   sub foo { 
     my $fh;
     open $fh , '<', 'file.txt' or Carp::croak('Cannot Open File.txt'); 
     # do stuff with $fh; 
     close $fh or Carp::carp('Something annoying in close :S '); 
   }

Note that if you omit close, the file will be closed as soon as $fh goes out of visibility.

Kent Fredric
Deleted my answer because this one very accurately answers it. Good explanation, Kent.
Frakkle
3-arg open *is not* a drop in replacement for 2-arg open. Don't use it if you want to be able to apply default layers provided by the open pragma or -C switch.
ysth
-C doesn't even work in 5.10 anymore. You should use explicit encodings instead.
Kent Fredric
+4  A: 

You need to save the STDOUT so you can restore it. This is from the ... perldoc for open:

Here is a script that saves, redirects, and restores STDOUT and STDERR using various methods:

#!/usr/bin/perl
open my $oldout, ">&STDOUT"     or die "Can't dup STDOUT: $!";
open OLDERR,     ">&", \*STDERR or die "Can't dup STDERR: $!";

open STDOUT, '>', "foo.out" or die "Can't redirect STDOUT: $!";
open STDERR, ">&STDOUT"     or die "Can't dup STDOUT: $!";

select STDERR; $| = 1;  # make unbuffered
select STDOUT; $| = 1;  # make unbuffered

print STDOUT "stdout 1\n";  # this works for
print STDERR "stderr 1\n";  # subprocesses too

open STDOUT, ">&", $oldout or die "Can't dup \$oldout: $!";
open STDERR, ">&OLDERR"    or die "Can't dup OLDERR: $!";

print STDOUT "stdout 2\n";
print STDERR "stderr 2\n";

Have a look on the "or die" parts. You should always test an open for errors...

+6  A: 

If I understand what you're trying to do, I think you want to use select. It allows you to switch between filehandles easily. You should also use the more modern form of open (3 arguments, lexical filehandles and error checking). See perldoc perlopentut.

#!/usr/bin/env perl
use strict;
use warnings;

open my $fh,  '>', 'abc' or die "Can't open 'abc': $!";
open my $fh2, '>', 'def' or die "Can't open 'def': $!";
open my $fh3, '>', 'xyz' or die "Can't open 'xyz': $!";

select $fh;
print "First\n";

# Later
select $fh2;
print "Second\n";

# Later
select $fh3;
print "Third\n";

# Later
select STDOUT;
print "Fourth\n";
Telemachus
+3  A: 

Localize STDOUT. Then you can alias it in a limited, dynamic scope. Once you leave that scope, STDOUT will return to normal.

use strict;
use warnings;

print_stuff('Normal STDOUT');

{   local *STDOUT;
    open( STDOUT, '>', 'out' ) 
         or die "Can't redirect STDOUT: $!";
    local $| = 1;  # Unbuffer handle. 
                   # Do this AFTER redirecting STDOUT.

    print_stuff('Aliased to out');

    sleep 10;
} 


print_stuff('Back to normal STDOUT');

sub print_stuff {
    print join "\n", @_, '';
}
daotoad
A: 

If I'm only going to be using a file handle for a short time, I will put it inside an anonymous block. Which automatically limits the scope of any non-global variables.

{
  open my $fh, '>', 'abc';
  print $fh "assorted data";
  close $fh;
}

As an added benefit, you don't even need to have to have the  'close $fh;'  line, because it would be automatically closed when the variable goes out of scope.

If you are going to use this technique, you should always localize any global variables that are modified inside the code block.

{
  local *FH;
  open FH, '>', 'abc';
  print FH "assorted data";
  close FH;
}
Brad Gilbert