This short example illustrates a problem I have in Perl. The idea is to process stdin as default or use an 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 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:
$fp is equal to *main::STDIN and not to the current opened file?'a' is not read?Logically, $fp is local to the subroutine. It is first assigned to *STDIN then altered by open with a file pointer to a. Then I process b. When I return to the processing of b I should still have a pointer to a inside $fp.
I've read here that handler passed to open must be an undefined scalar. However it seems to work with b and c.
This has to be to do with reassigning 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 output:
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 that one line back to:
my $fp = *STDIN;
And you get Dumper reporting (rest of output snipped for brevity):
Process before open a: $VAR1 = *::STDIN
Process after open a: $VAR1 = *::STDIN;
However it clearly is opening, because it's printing the file content.
If you fire up strace and run through the two processes (cut down thus):
#!/usr/bin/env perl
my $fh;
open ( $fh, "<", "fishfile" ) or warn $!;
print <$fh>;
And run this strace myscript. (Note - strace is a linux specific tool - there are others for other platforms)
(note - I'm using a file called fishfile with a content of fish because that way I am pretty sure I can find the text :))
Doing it twice - once with the assigning STDIN you'll see a couple of differences around the open operation. Run them both through diff and you'll see there's lot of it but the interesting part is:
Without the STDIN assignment:
open ( "fishfile", O_RDONLY) = 3
read (3, "fish\n", 8192 ) = 5
write ( 1, "fish\n", 5 ) = 5
With the STDIN assignment:
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 - return code for open is the file descriptor number - e.g. 3)
So what is it actually doing is:
STDIN) STDIN. 1 or STDOUT. (2 is STDERR). So as a result - because you are - by doing this - clobbering STDIN with your own file descriptor, and because STDIN is globally scoped (rather than your $fh which is lexically scoped):
STDIN in sub process b and then reading it until EOF, which means when a gets to start reading it, there's nothing there. If however you move the open to after the call to 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";
}
That works successfully. I'm assuming based on your previous questions this is related to processing a file, then opening sub processes based on the content.
The solution would therefore be test for the existence of $name before cloning STDIN and you won't have the the problem.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With