Bad value in file pointer
This short example illustrates a problem in Perl. The idea is to handle stdin
the default or use the input file if specified.
#!/usr/bin/env perl
qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};
process('a');
process('c');
sub process {
my $name = shift;
my $fp = *STDIN;
open $fp, '<', $name if $name;
process('b') if $name eq 'a';
print "Processing file '$name' (fp=$fp)\n";
print while(<$fp>);
}
The output I get is:
$ ./curious.pl
Processing file 'b' (fp=*main::STDIN)
b file
Processing file 'a' (fp=*main::STDIN)
Processing file 'c' (fp=*main::STDIN)
c file
And it should be:
$ ./curious.pl
Processing file 'b' (fp=*main::STDIN)
b file
Processing file 'a' (fp=*main::STDIN)
a file
Processing file 'c' (fp=*main::STDIN)
c file
I am probably missing two things:
- Why
$fp
is it equal*main::STDIN
and not the current open file? - Why is
'a'
n't the content readable?
Logically $fp
local to the subroutine. It is assigned first *STDIN
, then changed to open
with a file pointer to a
. Then I process it b
. When I go back to processing b
, I should still have a pointer to a
inside $fp
.
I read here that the handler passed to open
must be an undefined scalar. However, it works with b
and c
.
source to share
This should be related to reassignment STDIN
:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};
process('a');
process('c');
sub process {
my $name = shift;
print "Starting process with $name from ", scalar caller(), " \n";
my $fp; # = *STDIN;
print "Process before open $name: ", Dumper($fp), "\n";
open $fp, '<', $name if $name;
print "Process after open $name: ", Dumper($fp), "\n";
process('b') if $name eq 'a';
print "Processing file '$name' (fp=$fp)\n";
print "Contents of $name:\n";
print while (<$fp>);
print "Done with $name\n\n\n";
}
This gives the result:
Starting process with a from main
Process before open a: $VAR1 = undef;
Process after open a: $VAR1 = \*{'::$fp'};
Starting process with b from main
Process before open b: $VAR1 = undef;
Process after open b: $VAR1 = \*{'::$fp'};
Processing file 'b' (fp=GLOB(0x136412c))
Contents of b:
"b file"
Done with b
Processing file 'a' (fp=GLOB(0x606f54))
Contents of a:
"a file"
Done with a
Starting process with c from main
Process before open c: $VAR1 = undef;
Process after open c: $VAR1 = \*{'::$fp'};
Processing file 'c' (fp=GLOB(0x136412c))
Contents of c:
"c file"
Done with c
If you do the same, but just change this line to:
my $fp = *STDIN;
And you get the Dumper report (the rest of the abbreviated output is abbreviated):
Process before open a: $VAR1 = *::STDIN
Process after open a: $VAR1 = *::STDIN;
However, it explicitly opens because it prints the contents of the file.
If you start strace
and run two processes (cut this way):
#!/usr/bin/env perl
my $fh;
open ( $fh, "<", "fishfile" ) or warn $!;
print <$fh>;
And run this one strace myscript
. (Note: strace
this is a Linux specific tool - there are others for other platforms)
(note - I'm using a file named fishfile
with content fish
because I'm sure I can find the text :))
Doing this twice - once with the assignment, STDIN
you will see a couple of differences around the operation open
. Follow them through diff
and you will see a lot, but an interesting part:
Without appointment STDIN
:
open ( "fishfile", O_RDONLY) = 3
read (3, "fish\n", 8192 ) = 5
write ( 1, "fish\n", 5 ) = 5
With the appointment STDIN
:
open ( "fishfile", O_RDONLY) = 3
dup2 ( 3, 0 ) = 0
close ( 3 ) = 0
read (0, "fish\n", 8192 ) = 5
write ( 1, "fish\n", 5 ) = 5
(Note - the return code for open
is the file descriptor number - for example, 3)
So what it actually does:
- opening a new file
- duplicating it by file descriptor zero (which
STDIN
) - reading new
STDIN
. - write it to a file descriptor
1
orSTDOUT
. (2
-STDERR
).
So in the end - because you are - by doing this - clobbering STDIN
with your own file descriptor and because it STDIN
has a global scope (not yours $fh
, which is lexically limited):
- You mask
STDIN
in a subprocessb
and then read it to EOF, which means when youa
start reading it, there is nothing there.
If you move open
after the call b
:
sub process {
my $name = shift;
print "Starting process with $name from ", scalar caller(), " \n";
my $fp = *STDIN;
process('b') if $name eq 'a';
print "Process before open $name: ", Dumper($fp), "\n";
open $fp, '<', $name if $name;
print "Process after open $name: ", Dumper($fp), "\n";
print "Processing file '$name' (fp=$fp)\n";
print "Contents of $name:\n";
print while (<$fp>);
print "Done with $name\n\n\n";
}
It works well. I am guessing based on your previous questions, this has to do with processing the file and then opening the helper processes based on the content.
This way, the solution should be tested for existence $name
before cloning STDIN
and you won't have a problem.
source to share
my $fp = *STDIN;
open $fp, '<', $name if $name;
coincides with
my $fp = *STDIN;
open STDIN, '<', $name if $name;
You are instructing open
to change STDIN
. If you want to create a new descriptor, you need to pass the undefined scalar to open
.
use strict;
use warnings;
qx{echo "a file" > a};
qx{echo "b file" > b};
qx{echo "c file" > c};
process('a');
process('c');
process('');
sub process {
my $name = shift;
my $fp;
if( defined( $name ) and length( $name ) ) {
open $fp, '<', $name
}else{
$fp = *STDIN;
}
process('b') if defined($name) and $name eq 'a';
print "Processing file '$name' (fp=$fp)\n";
print while(<$fp>);
}
Tested with echo stdin | perl scriptname
.
WARNING Go to if length($name)
c if $name
to avoid "misbehavior" with a file named 0
.
source to share