4

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.

Ether
  • 53,118
  • 13
  • 86
  • 159
Ryan C. Thompson
  • 40,856
  • 28
  • 97
  • 159
  • It's not clear what behavior of <> you're looking for. As far as I know, the only difference between <> and <$filehandle> is that <> will fall back on filename arguments (@ARGV) if they're given. Is that what you mean? – dlowe Oct 29 '09 at 00:42
  • I think he wants to pass the `<>` filehandle to functions. – Chris Lutz Oct 29 '09 at 00:46
  • Aahhhh, I see now. I read that question backwards... – dlowe Oct 29 '09 at 00:48
  • Yes, Chris, that's exactly what I *had wanted* to do. But I ended up using `local @ARGV` and then just reading from ``. Thanks, everybody. – Ryan C. Thompson Oct 29 '09 at 16:47

3 Answers3

9

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.

Community
  • 1
  • 1
Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • 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 Oct 29 '09 at 01:06
  • 1
    The nice thing about `<>` is if there are no filenames, it reads from `STDIN`. – Sinan Ünür Oct 29 '09 at 01:14
  • 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 Oct 29 '09 at 01:23
  • 1
    Actually, I later found out that `<>` is just a shortcut for ``. 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 C. Thompson Oct 29 '09 at 05:19
6

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
  • 116,958
  • 15
  • 196
  • 339
1

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 C. Thompson
  • 40,856
  • 28
  • 97
  • 159