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

My question is similar to this one.

I have a file, in which I am trying to check syntax, 6 lines at a time. The file will vary in length and may contain comments and/or empty lines.

I'm using Text::Template (thanks to a few of you) to create a template of what each 6-line block should look like:

{$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri2} {$proto}://{$uri1} {$map} {$proto}://{$uri2} {$proto}://{$uri1}

Here's some sample data that I am working with:

map http://chat.yahoo.com http://origin-chat.yahoo.com map tunnel://chat.yahoo.com tunnel://origin-chat.yahoo.com map http://10.0.2.7 http://origin-chat.yahoo.com map tunnel://10.0.2.7 tunnel://origin-chat.yahoo.com reverse_map http://origin-chat.yahoo.com http://chat.yahoo.com reverse_map tunnel://origin-chat.yahoo.com tunnel://chat.yahoo.com map http://shop.yahoo.com http://origin-shop.yahoo.com map tunnel://shop.yahoo.com tunnel://origin-shop.yahoo.com map http://10.0.2.8 http://origin-shop.yahoo.com map tunnel://10.0.2.8 tunnel://origin-shop.yahoo.com reverse_map http://origin-shop.yahoo.com http://shop.yahoo.com reverse_map tunnel://origin-shop.yahoo.com tunnel://shop.yahoo.com

Here's the program, thus far:

#!/usr/bin/perl -w use strict; use Text::Template; my %vars; my $lines; my @lines; my $file = "/home/trixee/remap.config"; my $template = new Text::Template (TYPE => 'FILE' , SOURCE => $file) or die "Couldn't construct template: $Text::Template::ERROR"; open FH, "$file" or die "$!\n"; while ($lines = <FH>) { chomp $lines; next if ($lines =~ /^\#/); if ($lines=~/(\d+)(\s+)(map|reverse_map)(\s+)(\w+)\:\/\/(.*?)(\s+)(\5 +)\:\/\/(.*?)$/) { $vars{'d'} = $1; $vars{'map'} = $3; $vars{'proto'} = $5; $vars{'uri1'} = $6; $vars{'uri2'} = $8; } } my $result = $template->fill_in(HASH => \%vars); if (defined $result) { print $result } else { die "Couldn't fill in template: $Text::Template::ERROR" }

Using the example data above, I would need to modify the existing code to read lines 1-6 and populate the template. Then, I would add logic to compare the populated template with lines 1-6 of the data file. If they are equal, then read in lines 7-12, and repeat the comparison logic.

Any ideas? Am I appoaching this efficiently?

Replies are listed 'Best First'.
Re: Reading in N lines at a time
by dvergin (Monsignor) on Aug 15, 2001 at 09:27 UTC
    tachyon has answered your specific question. But here are come general suggestions that you may find helpful. Mostly pertaining to the regex and the code lines following it.

    The var name '$lines' is confusing. (I find myself asking what you are trying to tell me -- is it really more than one line each time?) I'd just call it '$line'.

    Your regex does not line up with the sample data you have shown (no leading field of digits in the sample) but you'll sort that out on your own.

    In the regex, you don't need to escape ':' and if you use a different regex delimiter, you don't need to escape '/'.

    Also, you don't need to put parentheses around (\s+) since you are not capturing these for use later. And omitting those makes the fields you do want to capture a little spiffier to recover as explained below.

    When capturing several values into a hash at once, a hash slice is a slick way to condense code. The verbose way to do this here would be:

    my %vars; @vars{'d','map','proto','uri1','uri2'} = ($1,$3,$5,$6,$8);
    But since we dropped the unneeded parens around the spaces, the desired fields are now just $1,$2,$3,$4,$5. That makes it easy to grab them directly by pre-loading them into another array.

    So this:

    if ($lines=~/(\d+)(\s+)(map|reverse_map)(\s+)(\w+)\:\/\/(.*?)(\s+)(\5) +\:\/\/(.*?)$/) { $vars{'d'} = $1; $vars{'map'} = $3; $vars{'proto'} = $5; $vars{'uri1'} = $6; $vars{'uri2'} = $8; } } my $result = $template->fill_in(HASH => \%vars);
    Becomes this. (Untested... but probably pretty close):
    my @fields = qw(d map proto uri1 uri2); if (@vars{@fields} = $line=~m#(\d+)\s+(map|reverse_map)\s+(\w+)://(.*? +)\s+(\5)://(.*?)$#) { my $result = $template->fill_in(HASH => \%vars); }
    I may have taken liberties with the logic of your curly-braces, but you get the idea.

    HTH -- David

      Hey, thanks for making time to point this out to me. I find it very helpful. Yeah, the live data doesn't contain leading digits; I intentionally put them there, in order to make my test print statements a bit easier to read.
        You might consider using $. instead of the leading didget. It will give you the line number of the last line read without the need to alter the way your script parses it's input when you are done debuging.
      hmmm. Something is wrong that I can't figure out:

      #!/usr/bin/perl -w use strict; use Text::Template; my %vars; my $line; my @fields; my $result; my $file = "/home/trixee/20010814/remap.tmpl"; my $cfg = "/home/trixee/20010814/remap.config"; my $template = new Text::Template (SOURCE => $file) or die "Couldn't construct template: $Text::Template::ERROR"; open FH, "$cfg" or die "$!\n"; while ($line = <DATA>) { chomp $line; next if ($line =~ /^\#/); @fields = qw(map proto uri1 uri2); if ( @vars{@fields} = $line=~m!(map|reverse_map)\s+(\w+)://(.*?)\s ++\2://(.*?)$! ) { $result = $template->fill_in(HASH => \%vars); } if ($result) { print "$result\n"; } else { die "Couldn't fill in template: $Text::Template::ERROR" } } __DATA__ # Automatic mapping tables generated from Magma map http://www.digitalcity.com http://origin-www.digitalcity.com map tunnel://www.digitalcity.com tunnel://origin-www.digitalcity.com map http://10.0.2.1 http://origin-www.digitalcity.com map tunnel://10.0.2.1 tunnel://origin-www.digitalcity.com reverse_map http://origin-www.digitalcity.com http://www.digitalcity.c +om reverse_map tunnel://origin-www.digitalcity.com tunnel://www.digitalci +ty.com Template: {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri2} {$proto}://{$uri1} {$map} {$proto}://{$uri2} {$proto}://{$uri1}

      prints

      map http://www.digitalcity.com http://origin-www.digitalcity.com map http://www.digitalcity.com http://origin-www.digitalcity.com map http://www.digitalcity.com http://origin-www.digitalcity.com map http://www.digitalcity.com http://origin-www.digitalcity.com map http://origin-www.digitalcity.com http://www.digitalcity.com map http://origin-www.digitalcity.com http://www.digitalcity.com map tunnel://www.digitalcity.com tunnel://origin-www.digitalcity.com map tunnel://www.digitalcity.com tunnel://origin-www.digitalcity.com map tunnel://www.digitalcity.com tunnel://origin-www.digitalcity.com map tunnel://www.digitalcity.com tunnel://origin-www.digitalcity.com map tunnel://origin-www.digitalcity.com tunnel://www.digitalcity.com map tunnel://origin-www.digitalcity.com tunnel://www.digitalcity.com map http://10.0.2.1 http://origin-www.digitalcity.com map http://10.0.2.1 http://origin-www.digitalcity.com map http://10.0.2.1 http://origin-www.digitalcity.com map http://10.0.2.1 http://origin-www.digitalcity.com map http://origin-www.digitalcity.com http://10.0.2.1 map http://origin-www.digitalcity.com http://10.0.2.1 map tunnel://10.0.2.1 tunnel://origin-www.digitalcity.com map tunnel://10.0.2.1 tunnel://origin-www.digitalcity.com map tunnel://10.0.2.1 tunnel://origin-www.digitalcity.com map tunnel://10.0.2.1 tunnel://origin-www.digitalcity.com map tunnel://origin-www.digitalcity.com tunnel://10.0.2.1 map tunnel://origin-www.digitalcity.com tunnel://10.0.2.1 reverse_map http://origin-www.digitalcity.com http://www.digitalcity.c +om reverse_map http://origin-www.digitalcity.com http://www.digitalcity.c +om reverse_map http://origin-www.digitalcity.com http://www.digitalcity.c +om reverse_map http://origin-www.digitalcity.com http://www.digitalcity.c +om reverse_map http://www.digitalcity.com http://origin-www.digitalcity.c +om reverse_map http://www.digitalcity.com http://origin-www.digitalcity.c +om reverse_map tunnel://origin-www.digitalcity.com tunnel://www.digitalci +ty.com reverse_map tunnel://origin-www.digitalcity.com tunnel://www.digitalci +ty.com reverse_map tunnel://origin-www.digitalcity.com tunnel://www.digitalci +ty.com reverse_map tunnel://origin-www.digitalcity.com tunnel://www.digitalci +ty.com reverse_map tunnel://www.digitalcity.com tunnel://origin-www.digitalci +ty.com reverse_map tunnel://www.digitalcity.com tunnel://origin-www.digitalci +ty.com

      Why?

        From your description you gave in the CB, you're looking for it to print 6 lines according to the layout of the template.

        It is doing that, bu not as I assume you wish. I'm not all that knowledgeable about Text::Template, but this is what I see happening. You're defining your fields..

        @fields = qw(map proto uri1 uri2);
        ..then you are parsing and passing your data..
        if ( @vars{@fields} = $line=~m!(map|reverse_map)\s+(\w+)://(.*?)\s ++\2://(.*?)$! ) { $result = $template->fill_in(HASH => \%vars); }
        ...and your template looks like this..
        {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri1} {$proto}://{$uri2} {$map} {$proto}://{$uri2} {$proto}://{$uri1} {$map} {$proto}://{$uri2} {$proto}://{$uri1}
        Now, you read one(1) line, parsed it and passed it to fill in the template. For the first line, your values are going to look like this..
        $map => map $proto =>http $uri1 => www.digitalcity.com $uri2 => origin-www.digitalcity.com
        When you pass it to $template->fill_in(), it fills in the whole template. So on the line, the template is filled in completely (all six lines) and the results are the first 6 that are printed. Rinse, wash and repeat for the rest of the lines you're parsing.

        I assume that this isn't the effect that you want. But to fix it you're going to have to take a different approach. Namely, reading in 6 lines and using different variables on each line to designate which values go where.

        There might be a better way using Text::Template but I'm really not all that familiar with the module.

        Hope this helps..
        Rich

Re: Reading in N lines at a time
by tachyon (Chancellor) on Aug 15, 2001 at 07:02 UTC

    Here is a little sub to read in 'n' lines at a time from a given filehandle - I use DATA for the demo.

    sub get_n_lines { local *FH = shift; my $n = shift; my @lines; for (1..$n) { my $line = <FH>; push @lines, $line if defined $line; } return @lines; } while (my @lines = get_n_lines(*DATA, 6)) { print "Got:\n", @lines , "\n"; } __DATA__ line 1 line 2 line 3 line 4 line 5 line 6 line 7 line 8 line 9 line 10 line 11 line 12 line 13 line 14 line 15 line 16 line 17 line 18

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Reading in N lines at a time
by Ven'Tatsu (Deacon) on Aug 16, 2001 at 12:23 UTC
    This will read 6 lines, then let you do something else then return to reading another 6 lines until the end of the file. (start up code and Template code omited as the former is not changed and I don't know any thing about the latter =)
    ... until (eof(FH)) { for my $i (1..6) { my $line = <FH>; last unless defined $line; chomp $line; redo if ($line =~ /^\s*(?:#|$)/); if ($line=~m#(map|reverse_map)\s+(\w+)://(\S*?)\s+(\2)://(\S*? +)$#) { $vars{'d'} = $.; $vars{'map'} = $1; $vars{'proto'} = $2; $vars{'uri1'} = $3; $vars{'uri2'} = $4; } } #do some thing with $vars before here. #6 lines (or as many as remained in the file) will have been read +at this point }