views:

277

answers:

3

I like to use the nifty perl feature where reading from the empty angle operator <> magically gives your program UNIX filter semantics, but I'd like to be able to access this feature through an actual filehandle (or IO::Handle object, or similar), so that I can do things like pass it into subroutines and such. Is there any way to do this?

This question is particularly hard to google, because searching for "angle operator" and "filehandle" just tells me how to read from filehandles using the angle operator.

+9  A: 

From perldoc perlvar:

  • ARGV

The special filehandle that iterates over command-line filenames in @ARGV. Usually written as the null filehandle in the angle operator <>. Note that currently ARGV only has its magical effect within the <> operator; elsewhere it is just a plain filehandle corresponding to the last file opened by <>. In particular, passing \*ARGV as a parameter to a function that expects a filehandle may not cause your function to automatically read the contents of all the files in @ARGV.

I believe that answers all aspects of your question in that "Hate to say it but it won't do what you want" kind of way. What you could do is make functions that take a list of filenames to open, and do this:

sub takes_filenames (@) {
  local @ARGV = @_;
  // do stuff with <>
}

But that's probably the best you'll be able to manage.

Chris Lutz
Of course, I forgot the other answer, which is to make a class that takes many filenames and overloads the `<>` operator to read over them the same way that `<>` does with `@ARGV`. It even has some super extendability potential.
Chris Lutz
The nice thing about `<>` is if there are no filenames, it reads from `STDIN`.
Sinan Ünür
Yeah, we would potentially lose that (it'd be awkward to add, at the very least), but we'd gain the ability to pass it around.
Chris Lutz
Actually, I later found out that `<>` is just a shortcut for `<ARGV>`. I had always thought it was magic. Now I know that the *real* magic is in the `ARGV` filehandle. But anyway, I eventually realized that there was a better way to structure my program. I did use the `local @ARGV` trick, though.
Ryan Thompson
+6  A: 

Expanding on Chris Lutz's idea, here is a very rudimentary implementation:

#!/usr/bin/perl

package My::ARGV::Reader;

use strict; use warnings;
use autodie;
use IO::Handle;

use overload
    '<>' => \&reader,
    '""' => \&argv,
    '0+' => \&input_line_number,
;

sub new {
    my $class = shift;
    my $self = {
        names => [ @_ ],
        handles => [],
        current_file => 0,
    };
    bless $self => $class;
}

sub reader {
    my $self = shift;

    return scalar <STDIN> unless @{ $self->{names}};

    my $line;

    while ( 1 ) {
        my $current = $self->{current_file};
        return if $current >= @{ $self->{names} };

        my $fh = $self->{handles}->[$current];

        unless ( $fh ) {
            $self->{handles}->[$current] = $fh = $self->open_file;
        }

        if( eof $fh ) {
            close $fh;
            $self->{current_file} = $current + 1;
            next;
        }

        $line = <$fh>;
        last;
    }
    return $line;
}

sub open_file {
    my $self = shift;
    my $name = $self->{names}->[ $self->{current_file} ];
    open my $fh, '<', $name;
    return $fh;
}

sub argv {
    my $self = shift;
    my $name = @{$self->{names}}
             ? $self->{names}->[ $self->{current_file} ]
             : '-'
             ;
    return $name;
}

sub input_line_number {
    my $self = shift;
    my $fh = @{$self->{names}}
           ? $self->{handles}->[$self->{current_file}]
           : \*STDIN
           ;
    return $fh->input_line_number;
}

which can be used as:

package main;

use strict; use warnings;

my $it = My::ARGV::Reader->new(@ARGV);

echo($it);

sub echo {
    my ($it) = @_;
    printf "[%s:%d]:%s", $it, +$it, $_ while <$it>;
}

Output:

[file1:1]:bye bye
[file1:2]:hello
[file1:3]:thank you
[file1:4]:no translation
[file1:5]:
[file2:1]:chao
[file2:2]:hola
[file2:3]:gracias
[file2:4]:
Sinan Ünür
Awww... [looks whistfully at the beginnings of own implementation...]
Chris Lutz
I really like your overload of string interpretation to print the current filename. That's really clever.
Chris Lutz
@Chris Lutz: Thank you. I finally managed to figure out how to access the current line number through overload. Fun stuff.
Sinan Ünür
A: 

It looks like this has already been implemented as Iterator::Diamond. Iterator::Diamond also disables the 2-argument-open magic that perl uses when reading <ARGV>. Even better, it supports reading '-' as STDIN, without enabling all the other magic. In fact, I might use it for that purpose just on single files.

Ryan Thompson