John Doe John Doe - 6 months ago 34
Perl Question

How can I read multiple lines of a file into blocks in Perl?

I have a file which contains the text below.

Image

I know how to make the lines into an array using Perl, but in this case I want to make an array with two elements. Each that begins with

#L_ENTRY
and ends with
#SYNONYM <0>
.

Can anyone help?

Answer

There are two ways to do it. Firstly, you can set the "input record separator" special variable (see more here). In short, you are telling perl that a line is not terminated by a new-line char. In your case, you could set it to '#SYNONYM <0>'. Then when you read in one line, you get everything up to that point in the file that has that tag - if the tag is not there, then you get what's left in the file. So, for input data that looks like this;

#L_ENTRY        <s_slash_1>
#LEX         </>
#ROOT        </>
#POS         <sp>
#SUBCAT      <slash>
#S_LINK            <>
#BITS     <>
#WEIGHT      <0.1>
#SYNONYM     <0>

#L_ENTRY        <s_comma_1>
#LEX         <,>
#ROOT        <,>
#POS         <sp>
#SUBCAT      <comma>
#S_LINK            <>
#BITS     <>
#WEIGHT      <0.1>
#SYNONYM     <0>

if you run this;

use v5.14;
use warnings;

my $filename = "data.txt" ;
open(my $fh, '<', $filename) or die "$filename: $!" ;
local $/ = "#SYNONYM     <0>\n" ;
my @chunks = <$fh> ;
say $chunks[0] ;
say '---' ;
say $chunks[1] ;

You get;

#L_ENTRY        <s_slash_1>
#LEX         </>
#ROOT        </>
#POS         <sp>
#SUBCAT      <slash>
#S_LINK            <>
#BITS     <>
#WEIGHT      <0.1>
#SYNONYM     <0>

---

#L_ENTRY        <s_comma_1>
#LEX         <,>
#ROOT        <,>
#POS         <sp>
#SUBCAT      <comma>
#S_LINK            <>
#BITS     <>
#WEIGHT      <0.1>
#SYNONYM     <0>

A couple of notes about this;

  1. Any extra data between your records is going to "get caught in the net" and end up at the start of each record;
  2. The record separator itself is still part of the data and is at the end of each record.

To get more control, it's better to process the data line-by-line and use regexs to switch between "capture" mode and "dont capture" mode:

use v5.14;
use warnings;

my $filename = "data.txt" ;
open(my $fh, '<', $filename) or die "$filename: $!" ;

my $found_start_token = qr/ \s* \#L_ENTRY \s* /x;
my $found_stop_token  = qr/ \s* \#SYNONYM \s+ \<0\> \s* \n /x;

my @chunks ;
my $chunk  ;
my $capture_mode = 0 ;

while ( <$fh> )  {
    $capture_mode = 1 if /$found_start_token/ ;
    $chunk .= $_ if $capture_mode ;
    if (/$found_stop_token/) {
        push @chunks, $chunk ;
        $chunk = '' ;
        $capture_mode = 0 ;
    }
}
say $chunks[0] ;
say '---' ;
say $chunks[1] ;
exit 0

A couple of notes;

  1. The program works by string concatenation of the current line, $_, on to $chunk if we're in caputure mode.
  2. Capture mode is turned off and on using regexs in 'extended mode', /x. This allows adding whitespace to the regex for easier reading.
  3. Extra data between record will not appear in the chunks.
  4. It produces the same output as before.
Comments