http://www.perlmonks.org?node_id=664708

citromatik has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

I usually design my scripts to get their input from a file. In some cases, specially when the script could be used in a pipe, I allow to get the input from STDIN when no file is specified at the command line. I do it this way:

use strict; use warnings; my $file = shift @ARGV; my $ifh; my $is_stdin = 0; if (defined $file){ open $ifh, "<", $file or die $!; } else { $ifh = *STDIN; $is_stdin++; } while (<$ifh>){ # Process } close $ifh unless $is_stdin;

I have included this in production code, is it safe?

Thanks in advance

citromatik

Replies are listed 'Best First'.
Re: input from STDIN or from a file
by Fletch (Bishop) on Jan 28, 2008 at 17:12 UTC

    Erm, you are aware that using just <> does pretty much the same thing already? Not to mention that while( <$ifh> ) { ... } isn't going to return false until there's an EOF condition on STDIN in which case you're not really going to gain much by not closing it (at least nothing useful that I can think of off hand).

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Erm, you are aware that using just <> does pretty much the same thing already?

      Yes, that's right, but in this particular script I use my filehandles in a closure:

      sub get_closure { my ($filename) = @_; open my $ifh, "<", $filename or die $!; return sub { # subroutine that returns one record per call } } # and then: my $getNextRecord = get_closure($filename); while ( my $record = $getNextRecord->() ){ # do something with $record }

      Yes, I know that this sounds paranoic, but I find some benefits using this (private filehandles, a bit of encapsulation, access to $ifh a bit faster, etc...

      Given this scenario I don't know if using <> is easy or even possible.

      Not to mention that while( <$ifh> ) { ... } isn't going to return false until there's an EOF condition on STDIN in which case you're not really going to gain much by not closing it (at least nothing useful that I can think of off hand).

      Sure, I agree, but you can have something like this:

      my $file = shift @ARGV; my $ifh; my $is_stdin = 0; if (defined $file){ open $ifh, "<", $file or die $!; } else { $ifh = *STDIN; $is_stdin++; } while (<$ifh>){ # Process } #close $ifh unless $is_stdin; close $ifh; ## code passes... ## ... and my $another_value = <STDIN>;

      If you happen to close STDIN, that last statement would give you an error.

      Ok, I will stop here, or you will think that I'm really a paranoic :-)

      citromatik

      also, there is a subtle difference between the <> operator and the implementation showed in the original post (although not very useful I think).

      consider this:

      $ cat test1.pl #!/usr/bin/perl use strict; use warnings; while (<>){ print; } $ cat > testfile two three $ echo "one" | perl test1.pl testfile one two three $ cat test2.pl #!/usr/bin/perl use strict; use warnings; my $file = shift @ARGV; my $ifh; my $is_stdin = 0; if (defined $file){ open $ifh, "<", $file or die $!; } else { $ifh = *STDIN; $is_stdin++; } while (<$ifh>){ print } close $ifh unless $is_stdin; $ echo "one" | perl test2.pl testfile two three

      That is, the <> operator acts like:

      unshift(@ARGV, '-') unless @ARGV; while ($ARGV = shift) { open(ARGV, $ARGV); while (<ARGV>) { ... # code for each line } }

      i.e. processes STDIN AND any file given in the command line, while my implementation processes the file given in the command line OR STDIN.

      citromatik

        Erm, no it does either arguments or STDIN if none were given just like the documentation in perlop and the code you copied there from says. Read it again:

        unshift( @ARGV, '-' ) unless @ARGV;

        Put a string '-' (which is shorthand to open to read from STDIN) onto the front of @ARGV unless there are already arguments in @ARGV.

        If you're getting the behavior you're seeing out of your test1.pl then something very strange is going on.

        $ cat my_test1.pl #!/usr/bin/perl use strict; use warnings; while (<>){ print; } $ cat infile 2 from infile 3 from infile $ echo "testing" | perl my_test1.pl infile 2 from infile 3 from infile

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: input from STDIN or from a file
by kyle (Abbot) on Jan 28, 2008 at 17:12 UTC

    I don't see anything wrong with it, but I normally just use the <> filehandle instead (see "I/O Operators" in perlop).

Re: input from STDIN or from a file
by naikonta (Curate) on Jan 28, 2008 at 17:16 UTC
    I don't know how complex your program would be, but I usually just use,
    $ cat file.txt a b c $ cat stdin.pl #!/usr/bin/perl -l while (<>) { $_ = uc; print; } $ perl stdin.pl file.txt A B C $ cat file.txt | perl stdin.pl A B C
    Or, just perl -lpe '$_=uc' if it's really that simple. Anyway, I don't see anything wrong with your code.

    Update: Fixed missing -l (only the dash was there). Thanks almut :-)


    Open source softwares? Share and enjoy. Make profit from them if you can. Yet, share and enjoy!

      #!/usr/bin/perl - ^

      I'm not sure you want the dash at the end of the shebang line...

      It doesn't do any harm when invoking the code like you did, but when I try to run your sample code/data without explicitly calling perl (rather letting the OS handle the shebang), I'm getting

      $ cat file.txt | ./664708.pl Can't locate object method "a" via package "b" (perhaps you forgot to +load "b"?) at - line 1.

      I suppose this is because what comes in through stdin is being interpreted as Perl code, rather than as content to be read via <> ...