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

Esteemed Monks,

Thus far you have all been extraordinarily helpful in my experiences with Perl and I have to once again ask for your assistance.

I have some code that I need to run, but I only want to write it once. I know that I can enclose is in subroutines like such:

if ($opt{'c'}) { open CATLOG, "<$MAILLOG"; while (my $line = <CATLOG>) { while_sub($line); } } else { $log = File::Tail->new( name => $MAILLOG, tail => -1); while (defined(my $line=$log->read)) { while_sub($line); } }

For my own coding style and the program flow and readability, I would prefer to do something like is below, however I know that this won't work (syntactically), but I believe it illustrates my goal. I want to just go through the file once (cat it) if $opt{'c'} is set, otherwise I want to tail it (using File::Tail).

if ($opt{'c'}) { open CATLOG, "<$MAILLOG"; while (my $line = <CATLOG>) { } else { $log = File::Tail->new( name => $MAILLOG, tail => -1); while (defined(my $line=$log->read)) { } ...while loop code here... } # End while loop close (CATLOG) if ($opt{'c'});

I am pretty sure that it would have something to do with BLOCK: { } expressions (even though I am not sure how to structure it) or potentially working an eval statement in there. Help. Thanks.

UPDATE:Thanks to all who helped. The key to this was tie. I now have it written as follows:

if ($opt{'c'}) { open MAILLOG_R, "<$MAILLOG" or die("Unable to open mail log: $!\n"); } else { tie *MAILLOG_R, 'File::Tail', (name => $MAILLOG, tail => -1); } while(my $line = <MAILLOG_R>) { ...blah... }

Note: The reason I use MAILLOG_R is because, using another PM, I tie *MAILLOG_W for consistancy.

Replies are listed 'Best First'.
Re: Looping based on a Conditional
by ikegami (Patriarch) on Jul 20, 2006 at 17:20 UTC
    use File::Tail (); { local *CATLOG; if ($opt{'c'}) { open CATLOG, '<', $MAILLOG or die("Unable to open mail log: $!\n"); } else { tie *CATLOG, 'File::Tail', (name => $MAILLOG, tail => -1); } while (<CATLOG>) { ...while loop code here... } }
    or (Added)
    use File::Tail (); use IO::File (); { # Why doesn't File::Tail implement IO::Handle's interface? # "read" means something else in Perl! package File::MyTail; BEGIN { our @ISA = 'File::Tail'; } sub getline { my $self = shift; return $self->read(@_); } } { my $log_fh; if ($opt{'c'}) { $log_fh = IO::File->new($MAILLOG, '<') or die("Unable to open mail log: $!\n"); } else { $log_fh = File::MyTail->new(name => $MAILLOG, tail => -1); } while (defined(my $line = $log_fh->getline())) { ...while loop code here... } }
    or (Added)
    use File::Tail (); { my $getline; if ($opt{'c'}) { open my $fh, '<', $MAILLOG or die("Unable to open mail log: $!\n"); $getline = sub { return scalar <$fh>; } } else { my $fh = File::MyTail->new(name => $MAILLOG, tail => -1); $getline = sub { return scalar $fh->read(); } } while (defined(my $line = $getline->()) { ...while loop code here... } }

    Untested.

    I like the last one. It has less overhead, and allows you to use the method of your choice to get a line from the source.

Re: Looping based on a Conditional
by shmem (Chancellor) on Jul 20, 2006 at 17:36 UTC
    I understand from what you write that you want to have just one while loop to read via a filehandle or File::Tail.

    Well then, just use the tie mode of File::Tail:

    if ($opt{'c'}) { open ( CATLOG, '<', $MAILLOG ) or die "Can't open $MAILLOG: $!\n"; } else { my $ref = tie *CATLOG, "File::Tail",( name => $MAILLOG, tail => -1 ) or die "Can't tie $MAILLOG: $!\n"; } while (my $line = <CATLOG>) { # ...while loop code here... }

    One more thing: Always check the return value of open / close.

    cheers,
    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Looping based on a Conditional
by diotalevi (Canon) on Jul 20, 2006 at 17:39 UTC

    Use a simple iterator.

    # Create an iterator... my $reader; if ( $opt{c} ) { open my $fh, '<', $MAILLOG or die "Can't open $MAILLOG: $!"; $reader = sub { <$fh> } } else { my $tail = File::Tail->new( name => $MAILLOG, tail => -1 ); $reader = sub { $tail->read }; } # Use our iterator, however it works... while ( my $line = $reader->() ) { ... }

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

Re: Looping based on a Conditional
by friedo (Prior) on Jul 20, 2006 at 17:15 UTC
    What's wrong with the subroutine approach? It certainly seems a lot clearer than any convoluted conditional-goto type of thing.

    The whole point of subroutines is so you don't have to write stuff more than once, after all.

      The reason I do not want to go with the subroutine approach is because this is part of the main body of the program. That would mean the main body just opens this loop and drops right into subroutines. My coding style (be it right or wrong), is that the main body of the program stays as the main body and a subroutine should be for a specific purpose, not to replace the main body of the program.
Re: Looping based on a Conditional
by izut (Chaplain) on Jul 20, 2006 at 17:13 UTC
    You're looking for redo:
    my $c = 0; BLOCK: { print "Hi there!\n"; redo BLOCK if $c++ < 10; }

    Igor 'izut' Sutton
    your code, your rules.

Re: Looping based on a Conditional
by imp (Priest) on Jul 20, 2006 at 17:40 UTC
    If the body of the loop is the same, and only the source changes, you can refactor the source instead of the loop body.

    Example implementation:

    use strict; use warnings; use File::Tail; my %opt = (c => 0); my $MAILLOG = $ARGV[0]; # # $reader will hold a reference to an anonymous subroutine that reads +from # a source. Either a filehandle or a File::Tail object. # my $reader = undef; if ($opt{'c'}) { my $fh = undef; open $fh, "<", $MAILLOG or die "Failed to read $MAILLOG. $!"; # Create an anonymous subroutine that reads from the lexical $fh # filehandle. Once $reader goes out of scope the file will be clos +ed $reader = sub { readline($fh) }; } else { my $log = File::Tail->new( name => $MAILLOG, tail => -1, interval => 1, max_interval => 1, ); # Create an anonymous subroutine that reads from the $log # object. Once $reader goes out of scope the object will be # destroyed. $reader = sub { $log->read() }; } while (defined(my $line=$reader->())) { print $line; }