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

Dear Monks,

I have searched high and low and cannot find the
answer to this problem I am having.

To demonstrate here's my code:

#!/usr/bin/perl -w use strict; my @names; my @items; sub processRec { my $fh = shift; #ref to filehandle while(<$fh>) { chomp; s/\cM//; last if /^$/; last if /^\d-RECORD$/; push ( @names, /ITEMNAME-(.*)/); push ( @items, /ITEM \d-(.*)/); } } open( FH, "<data"); while(<FH>) { if ( /\d-RECORD/ ) { processRec(*FH); } } close(FH); for my $name ( @names ) { print "name = [$name]\n"; } for my $item ( @items ) { print "item = [$item]\n"; }

This works fine most of the time.
But here is a case where it doesn't work ...
$ cat data
1-RECORD
ITEMNAME-bob
ITEM 1-socks
ITEM 2-shoes

2-RECORD
ITEMNAME-bill
ITEM 1-hat
ITEM 2-hair piece
3-RECORD
ITEMNAME-mary
ITEM 1-roach clip

4-RECORD
ITEMNAME-jimmy
ITEM 1-peanuts
ITEM 2-tooth pick

Here's the output from that case
name = bob
name = bill
name = jimmy
item = socks
item = shoes
item = hat
item = hair piece
item = peanuts
item = tooth pick

What happened to mary?


If there was a new line after the line ...
ITEM 2-hair piece
... I wouldn't have a problem.

I suspect that I need to do something like this ...
sub processRec {
...
   if (/^\d-RECORD$/) {
      put_line_back ($_);
      last;
   };
...
}

Replies are listed 'Best First'.
Re: How do I re-read a line?
by Mr. Muskrat (Canon) on May 01, 2002 at 21:54 UTC
    Hmm, how about something like this instead?
    #!/usr/bin/perl -w use strict; my @names; my @items; sub processRec { my $fh = shift; #ref to filehandle while(<$fh>) { chomp; s/\cM//; next if /^$/; next if /^\d-RECORD$/; push ( @names, /ITEMNAME-(.*)/); push ( @items, /ITEM \d-(.*)/); } } open( FH, "<data"); processRec(*FH); close(FH); for my $name ( @names ) { print "name = [$name]\n"; } for my $item ( @items ) { print "item = [$item]\n"; }

    No sense in using two while loops.


    Matthew Musgrove
    Who says that programmers can't work in the Marketing Department?
    Or is that who says that Marketing people can't program?
      Thanks Mr. Muskrat,

      Your solution works. But what if I was only able to
      edit the sub processRec?

      I am sorry for not stating that in my original post and
      thanks for your reply! :)
Re: How do I re-read a line?
by Kanji (Parson) on May 01, 2002 at 21:58 UTC

    You can use seek to 'rewind' a file handle...

    while(<$fh>) { # Make a note of the line length # in case we need to rewind $fh my $line = length; chomp; # ... if ( /^\d-RECORD$/ ) { # rewind $fh to the beginning of # the current line, so <$fh> can # re-get it. seek( $fh, 0 - $line, 1 ); last; } # ... }

        --k.


(jeffa) Re: How do I re-read a line?
by jeffa (Bishop) on May 01, 2002 at 22:23 UTC
    At the risk of posting code that you possibly can't use, how about a completely different approach. Instead of storing the names in one array and the items in other, how about creating a data structure that is a List of Hashes (LOH). Each hash has a 'name' key that points to a scalar, and an 'item' key that points to an array reference. Now you have a usuable data structure.

    The idea is to store the names and the items into the hash, and when a new record is read, simply store the hash. The problem is that you have (well, i have to - this is an invitation for a more elegant solution should someone wish to show me a new trick ;)) discard the first line and store the last record outside of the while loop:

    use strict; use Data::Dumper; my (@record, $discard, %temp); open(FH, 'data') or die 'file not found'; # discard the first line $discard = <FH>; while(<FH>) { chomp; s/\cM//; if (/^\d-RECORD$/) { push @record, {%temp}; undef %temp; } elsif (/ITEMNAME-(.*)/) { $temp{name} = $1; } elsif (/ITEM \d-(.*)/) { push @{$temp{item}}, $1; } } # the last record needs to be added outside while loop push @record, {%temp}; print Dumper \@record; # print items of last record print join(', ', @{$record[-1]{item}}), "\n";
    If someone knows of a more elegant way, please share. I tried using File::ReadBackwards, but then extra work is needed to reverse the items ...

    UPDATE:
    Regarding your regexes - you might want to consider changing \d to \d+ so that you can catch numbers greater than 9, and you probably should use \s or \s+ instead of a literal space.

    UPDATE: UPDATE:
    Thanks belg4mit!

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      This seems to work fine for me...
      my($index, $record) = (0); while (<$fh>) { chomp; s/\cM//; if (/^(\d)-RECORD$/) { $index = $1; $record->[$index]->{items} = []; } elsif (/ITEMNAME-(.*)/) { $record->[$index]->{name} = $1; } elsif (/ITEM \d-(.*)/) { push @{$record->[$index]->{items}}, $1; } }
      UPDATE: jeffa pointed out an undef in the record set. that's due to the lack of a 0-RECORD. Ignore it, or do $index = $1 -1, as you will.

      --
      perl -pew "s/\b;([mnst])/'$1/g"

Re: How do I re-read a line?
by Dog and Pony (Priest) on May 01, 2002 at 21:57 UTC
    I admit I didn't test this, but wouldn't
    next if /^\d-RECORD$/;
    work in this particular case? Actually, if you also did
    next if /^$/;
    it would seem that you wouldn't need the external check for /^\d-RECORDS$/. This is all based on the code I can see, of course, you may have other reasons for wanting to process each record separately, with one sub call each? Otherwise, I'd restructure my logic a bit.

    Update: to see what I mean, listen to the not-as-lazy-as-me Mr. Muskrat. :)

    Hope that helps. :)


    You have moved into a dark place.
    It is pitch black. You are likely to be eaten by a grue.