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

I process a file using the
while(<INHANDLE>){}
construct. I would like the block to iterate through one last time after EOF, perhaps with $_ null or set to explicit text "AFTERFILE".
Is there a simple way to accomplish this?

Thanks

Replies are listed 'Best First'.
Re: Continuing While Loop One Iteration After EOF
by ikegami (Patriarch) on Dec 21, 2005 at 17:21 UTC

    Basically, you want to

    1. get a line (or undef),
    2. process the line, and
    3. loop if appropriate,

    in that order. The following snippets will do just that:

    { local $_; do { $_ = <INHANDLE>; ... } while defined $_; }
    for (;;) { # Loop until "last". local $_ = <INHANDLE>; ... last if not defined $_; }
    { # Loop while "redo". local $_ = <INHANDLE>; ... redo if defined $_; }
    my $block = sub { ... }; &$block while <INHANDLE>; &$block foreach undef;

    All of the snippets set $_ to undef on the last pass.

    All of the snippets have access to the lexical variables, package variables and @_ of the parent scope.

    Update: Moved local outside of the loop in first snippet.

Re: Continuing While Loop One Iteration After EOF
by BrowserUk (Patriarch) on Dec 21, 2005 at 18:16 UTC

    If you recognise that the body of the while loop is effectively a subroutine with implicit parameters, then it becomes natural to think of what you describe as (crudely):

    doBody( $_ ) while( <FILEHANDLE> ); doBody( undef );

    Which makes it clear that

    1. you are calling (some part(s) of) the body from two different locations;
    2. you want the body to take different actions dependent up when it is called;

      Ie. When processing the file, or afterwards.

    3. Your "$_ == NULL" is a flag used just to decide when to do which type of processing.

    That probably indicates that there is some subset of the processing inside the body of the loop that should really be factored into a separate subroutine, rather than inlined. Leading to

    while( <FILEHANDLE> ) { do some stuff; doTheFactoredStuff(); do some other stuff; } doTheFactoredStuff();

    Which may suggest a better way of doing things--or not:)


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Thanks everyone for the replies. Functionally, the subroutine path does make good sense and probably should be the way to implement this.
      Procedurely, ikegami's earlier suggestion with the for(;;){} construct worked like a charm. Thanks again to all.
Re: Continuing While Loop One Iteration After EOF
by ruoso (Curate) on Dec 21, 2005 at 17:37 UTC

    Hmmmm...

    Two possible ways I could think of...

    my $i = 1; while ((my $line = <INHANDLE>) || $i--) {}

    or, just what has been said here

    while (my $line = <INHANDLE>) { } continue { dosomething() if eof(INHANDLE); }

    I would prefer the first, as the continue solution wouldn't enable the reuse of the while block.

    daniel
      while (my $line = <INHANDLE>) { ... } continue { ... if eof(INHANDLE); }

      is no different than

      while (my $line = <INHANDLE>) { ... } ...

      (barring reading errors), so it's not very useful. The code will be duplicated, which seems to be what the OP wants to avoid.

        Hmmmm... sure... Actually I spent 2 minutes to understand your post, before I could see the "..." after the while block. Indeed, I keep with the $i-- solution...

        daniel
Re: Continuing While Loop One Iteration After EOF
by traveler (Parson) on Dec 21, 2005 at 19:58 UTC
    What's wrong with:
    while(( $_ = <INHANDLE>) || ($_ = 'AFTERFILE') ){ stuff; }
    Assuming, as you imply, that the body of the loop will know what to do when it sees AFTERFILE.

      You should beware of lines like 0 at the end of the file, without a newline :-) But that's about the only thing that can fail for this approach...

      That will loop through the file, but then it will continue to loop forever with 'AFTERFILE' in $_. So if you're going to do that, you'd have to do it as
      while (defined($_ = <INHANDLE>) || ($_ = "AFTERFILE")){ stuff; last if $_ eq "AFTERFILE"; }
      and of course, if the file actually contains AFTERFILE, you're in trouble.

      So if you don't mind undef as your special after loop value, it's probably easiest to do something like

      LOOP: { $_ = <>; stuff; redo LOOP if defined; }
        The OP suggested AFTERFILE and that's why I said "Assuming, as you imply, that the body of the loop will know what to do when it sees AFTERFILE."
Re: Continuing While Loop One Iteration After EOF
by swampyankee (Parson) on Dec 21, 2005 at 17:13 UTC

    What about continue?

    Checkingthe perldocs (perldoc perlsyn; look for "continue"), it looks like it may be what you need.

      Good suggestion, but I don't think it fits the bill. continue is ran on each iteration - I just want to run the loop one last time, from the top, after the last line in the file is processed.
        Then redo is just for that! Unfortunately you can't use it as simply as in
        redo if eof;
        since eof will continue to hold true and you won't run the loop just one last time. You'll have to add some more custom control instead:
        my ($i,$cnt); while (<$inhandle>) { print ++$i; last if $cnt; $cnt++, redo if eof; }
        To add to the other suggestions you have been given and with a big caveat: we always recommend to always read files in line by line and not to slurp them in all at once. But if you're confident your input will not be exceedingly large in size and/or you collect all lines anyway, you may just do the latter:
        for (<$infile>,'AFTERFILE') { ... }