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

.

+3


source to share


2 answers


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

    or STDOUT

    . ( 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 subprocess b

    and then read it to EOF, which means when you a

    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.

+5


source


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

.

+3


source







All Articles