Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad value in a file pointer

Tags:

perl

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:

  • Why does $fp is equal to *main::STDIN and not to the current opened file?
  • Why the content '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.

like image 623
nowox Avatar asked Dec 05 '25 14:12

nowox


1 Answers

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:

  • opening your new file
  • duplicating it over file descriptor zero (which is STDIN)
  • reading the new STDIN.
  • write it to file descriptor 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):

  • You are masking 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.

like image 82
Sobrique Avatar answered Dec 07 '25 15:12

Sobrique