Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

What's the right way to write a method which returns one line at a time from a file?

by Cody Fendant (Friar)
on Nov 22, 2020 at 04:21 UTC ( #11124000=perlquestion: print w/replies, xml ) Need Help??

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

I want to write a module such that I can do this with a certain file:

use MyModule; my $reader = MyModule->new(); ### MyModule opens a file behind the scenes while(my $line = $reader->get_next_line()){ print "here's the next line: $line\n"; } ### stop when we get to the end of the file, obviously

That is, keep the file handle open in the module and each time I call get_next_line() get one more line.

Clearly I can't keep re-opening the file every time. Do I use a module like Tie::File?

Thanks in advance.

  • Comment on What's the right way to write a method which returns one line at a time from a file?
  • Download Code

Replies are listed 'Best First'.
Re: What's the right way to write a method which returns one line at a time from a file?
by Fletch (Chancellor) on Nov 22, 2020 at 04:41 UTC

    Just stash the handle in a member variable and when you call get_next_line call readline on the stashed handle.

    package MyFile; use Moo; has _fh => { is => 'rw' }; sub BUILD { my( $self, @args ) = @_; $self->_fh( do { open( my $fh, q{<}, $self->frobnicate_path() ) or die qq{Can't open frobnicated path: $!\n}; $fh; } ); return; } sub frobnicate_path { my( $self ) = shift; return qq{WHATEVER.txt}; } sub get_next_line { my( $self ) = shift; return readline( $self->_fh ); } sub DEMOLISH { if( $self->_fh ) { close( $self->_fh ) or warn qq{Problem closing frobnicated path: $ +!\n}; } } 1; __END__

    Edit: fixed readline in get_next_line and added frobnicate_path stub.

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

      Thanks, that kickstarted my brain.

      I'm too old-school to do it your way but I did it this way in the end:

      sub get_filehandle { my $self = shift; $self->{file} = shift; open( my $fh, '<', $self->{file} ) or die "can't open $self->{file}"; return $fh; } sub get_lines { my $self = shift; $self->{file} = shift; ### get the filehandle if we don't already have one unless ( $self->{file_handle} ) { $self->{file_handle} = $self->get_filehandle( $self->{file} ); } if ( my $line = readline( $self->{file_handle} ) ) { return $line; } else { return; } }
        if ( my $line = readline( $self->{file_handle} ) ) { return $line; } else { return; }

        I appreciate old-school, but I think the quoted code will fail to return the last line of a file if it is '0' with no terminating newline. I haven't tested it, but wouldn't

        if (defined(my $line = readline( $self->{file_handle} ))) { return $line; } else { return; }
        or even just
            return readline($self->{file_handle});
        (readline returns undef at eof) be better?


        Give a man a fish:  <%-{-{-{-<

        $self->{file_handle} = $self->get_filehandle( $self->{file} );

        You can't reload or open a new file using the above. Additionally, you can end up having a discrepancy whereas $self->{file} points to one file and $self->{file_handle} to another. If you want that functionality, then I would set file_handle inside get_filehandle() with appropriate logic.

        Not to the topic, but there should NEVER an else after a return/exit/croak/die.

        A return/die/exit/croak will end the current scope immediately, making the else obfuscating the code that follows, as the code after the else block will never be executed if the if branch is taken.

        If I were a code reviewer, that code would be vetoed.


        Enjoy, Have FUN! H.Merijn
Re: What's the right way to write a method which returns one line at a time from a file?
by haukex (Bishop) on Nov 22, 2020 at 12:38 UTC
    while(my $line = $reader->get_next_line()){

    I just wanted to point out that this suffers from the same issue that AnomalousMonk correctly pointed out deeper in the thread: if the file ends on a line containing just "0" with no newline, this loop won't catch that. You would have to say while( defined( my $line = $reader->get_next_line() ) ) instead. Alternatively, note it's possible to overload the <> operator (note that overloaded <> in list context wasn't implemented until 5.18). See for example my use in Algorithm::Odometer::Tiny; you'd just have to change $self->() to your method call, and then you could write while( my $line = <$reader> ) and Perl will automatically add the defined call.

    By the way, in your post here, I don't understand the point of the two $self->{file} = shift; lines, especially the second one? Why change the filename while reading the file?

      By the way, in your post here, I don't understand the point of the two $self->{file} = shift; lines, especially the second one? Why change the filename while reading the file?

      Looking at it, you're right, but I think the second one, if you mean the one further down the page, is the one that needs to exist and the first one is the one which doesn't.

      I instantiate the module without naming the file, then call  get_lines with the file name. It doesn't need to be passed as an argument to  get_filehandle because it's already there.

      My brain was having a very bad day as you can probably tell.

        I instantiate the module without naming the file, then call get_lines with the file name. It doesn't need to be passed as an argument to get_filehandle because it's already there.

        Yes, you're right, because you call get_filehandle inside of get_lines, it actually could make sense to pass the filename to the get_lines call. However, I think it could potentially still be confusing because with the code you showed, if all the user uses is get_lines, it'll only ever open one file - say I call my $x = $obj->get_lines("foo.txt"), and then my $y = $obj->get_lines("bar.txt"), now $y contains the second line of foo.txt. So that's why it might be better to separate the two actions - opening the file and reading from it - into two methods.

        Update: The reason I questioned whether it makes sense to pass a filename to get_filehandle is that I was imagining $self->{file} to be an object property that might deserve its own setter, but that's not as important. BTW, you might want to consider renaming get_filehandle to something like open_file to make it more clear what the method is doing.

Re: What's the right way to write a method which returns one line at a time from a file?
by perl-diddler (Chaplain) on Nov 22, 2020 at 13:22 UTC
    What type of algorithm you use depends on what type of interface you want to work with.

    I.e. instead of returning a file handle, you might just want your reader routine to return the I/O. I wanted to run a command and read the output line at a time as if I ran the command on the command line and piped it into my perl-script. So I wrote a module called 'Cmd' that I can pass what I want 'run' as a param, and keep calling it with the same command until it returns "undef", like (note: the below was typed in 'raw', and not tested):

    use Cmd; use P; use Cmds qw(ip); # command finder that produces '$Ip' with abspath of +'ip' my @cmd = qw( $Ip addr list ); local $_; my %intf2addrs; my $intf; while ($_=Cmd->run(\@cmd)) { if (/^(\S+):/) { $intf=$1;next; } if (/^\s+inet\s([^\s]+)\s/) { next unless $intf; $int2addrs{$intf}=$1; P "intf %10s: %s", $intf, $1; } } ...
    The routine stored the cmdline as a hash key to the needed info to the commands output. It ran commands and stored the output for subsequent calls with the same params and returned undef when done.

    So -- how you want to do what you are doing, depends on what type of interface you want to use. At the time, I wanted something that conceptually was similar to me invoking the command on the command line and piping its output into my perl program -- except the invoking of the command was in perl.

    As is oft said of perl, there are many ways to solve a problem in perl.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://11124000]
Approved by marto
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (2)
As of 2021-01-18 00:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    Notices?