简体   繁体   中英

Save result from flip-flop in variable?

I have about 1kB of text from STDIN

my $f = join("", <STDIN>);

and I would like to get the content between open1 and close1 , so /open1/../close1/ comes to mind.

I have only seen it been used in one liners and in scripts in while-loops and $_ .

Question

How can I get the result from /open1/../close1/ in my script when everything is in $f ?

Capturing all matches with a single regular expression

If you want to capture all the lines between open1 and start1 markers (excluding the markers) , it is easily done with a single regular expression:

my $f = join("", <STDIN>);

my @matches = ( $f =~ m/\bopen1\b(.*?)\bclose1\b/gs );

for my $m (@matches) {
  print "$m";
}

where

  • s modifier treats the string as a single line;
  • g modifier captures all the matches;
  • (.*?) matches a group of any characters using the lazy quantifier

Using the range operator

The range operator (so-called flip-flop) is not very convenient for this task if you want to avoid capturing the markers, because an expression like /open1/ .. /close1/ returns true for the lines matching the patterns.

The expression /^open1$/ .. /^close1$/ returns false until /^open1$/ is true . The left regular expression stops being evaluated once it matches the line, and keeps returning true until /^close1$/ becomes true . When the right expression matches, the cycle is restarted. Thus, the open1 and close1 markers are included into $matches .

It is even less convenient, if the input is stored in a variable, because you will need to read the contents of the variable line by line, eg:

my $matches = "";
my @lines = split /\n/, $f;
foreach my $line (@lines) {
  if ($line =~ m/^open1$/ .. $line =~ m/^close1$/) {
    $matches .= "$line\n";
  }
}

Note, it is possible to use arbitrary Perl expressions as operands of the range operator. I wouldn't recommend this code, as it is not very efficient, and not very readable. At the same time it is easy to adapt the first example to the case where the open1 and close1 markers are included into the set of matches, eg:

my @matches = ( $f =~ m/\bopen1\b(.*?)\bclose1\b/gs );
for my $m (@matches) {
  print "open1${m}close1\n";
}

You can rewrite how $f is generated so that it takes advantage of the flip-flop inside a while loop:

my ( $f, $matched );
while ( <> ) {
    $f .= $_;
    $matched .= $_ if /open1/ .. /close1/;
}

You can also employ split . To get what is between the first pair of open1 and close1

my $open_to_close = (split /open1|close1/, $f)[1];

The delimiter can be either open1 or close1 , so returned is a list of three elements: before open1 , between them, and after close1 . We take the second element.


If there are more open1 / close1 pairs take all odd-indexed elements.

Either get the array as well

my @parts = split /open1|close1/, $f;

my @all_open_to_close = @parts[ grep { $_ & 1 } 0..$#parts ];

or get it directly from the list

my @all_open_to_close = 
    grep { CORE::state $i; ++$i % 2 == 0 }  split /open1|close1/, $f;

The state is a feature from v5.10 . If you already use that you don't need CORE:: prefix.

Another way is to create a new inputs stream out of the contents of $f .

open my $fh, '<', \$f;
while (<$fh>) {
    if (/open1/ .. /close1/) {
        ...
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM