7

When I run this code:

$_='xaxbxc';
if(/(x(?<foo>.))+/) {
    say "&: ", $&;
    say "0: ", $-{foo}[0];
    say "1: ", $-{foo}[1];
 }

I get:

&: xaxbxc
0: c
1:

I understand that this is how it's supposed to work, but I would like to be able to somehow get the list of all matches ('a', 'b', 'c') instead of just the last match (c). How can I do this?

JoelFan
  • 37,465
  • 35
  • 132
  • 205
  • Similar to [this question](http://stackoverflow.com/questions/3459721/regex-group-in-perl-how-to-capture-elements-into-array-from-regex-group-that-mat) – Cameron Nov 23 '10 at 17:02

5 Answers5

5

In situations like these, using embeded code blocks provides an easy way out:

my @match;
$_='xaxbxc';
if(/((?:x(.)(?{push @match, $^N}))+)/) {
    say "\$1: ", $1;
    say "@match"
}

which prints:

$1: xaxbxc
a b c
Eric Strom
  • 39,821
  • 2
  • 80
  • 152
  • Yeah, I ended up using this as my solution... but the backtracking issue is tricky... I had to move my code block further away from the capture to make sure it didn't get called during the backtracking – JoelFan Nov 23 '10 at 23:06
4

I don't think there is a way to do this in general (please correct me if I am wrong), but there is likely to be a way to accomplish the same end-goal in specific situations. For example, this would work for your specific code sample:

$_='xaxbxc';
while (/x(?<foo>.)/g) {
    say "foo: ", $+{foo};
}

What exactly are you trying to accomplish? Perhaps we could find a solution for your actual problem even if there is no way to do repeating captures.

Cameron
  • 96,106
  • 25
  • 196
  • 225
2

Perl allows a regular expression to match multiple times with the "g" switch past the end. Each individual match can then be looped over, as described in the Global Matching subsection of the Using Regular Expressions in Perl section of the Perl Regex Tutorial:

while(/(x(?<foo>.))+/g){
    say "&: ", $&;
    say "foo: ", $+{foo};
}

This will produce an iterated list:

&: xa
foo: a
&: xb
foo: b
&: xc
foo: c

Which still isn't what you want, but it's really close. Combining a global regex (/g) with you previous local regex probably will do it. Generally, make a capturing group around your repeated group, then re-parse just that group with a global regex that represents just a single iteration of that group, and iterate over it or use it as a list.

It looks like a question fairly similar to this one- at least in answer, if not forumlation- has been answered by someone much more competent at Perl than I: "Is there a Perl equivalent of Python's re.findall/re.finditer (iterative regex results)?" You might want to check the answers for that as well, with more details about the proper use of global regexes. (Perl isn't my language, I just have an unhealthy appreciation for regular expressions.)

Community
  • 1
  • 1
Adam Norberg
  • 3,028
  • 17
  • 22
1

The %- variable is used when you have more than one of the same named group in the same pattern, not when the a given group happens to be iterated.

That’s why /(.)+/ doesn’t load up $1 with each separate character, just with the last one. Same with /(<x>.)+/. However, with /(<x>.)(<x>.)/ you have two different <x> groups, so $-{x}. Consider:

% perl -le '"foobar" =~ /(?<x>.)(?<x>.)/; print "x#1 is $-{x}[0], x#2 is $-{x}[1]"'
x#1 is f, x#2 is o

% perl -le '"foobar" =~ /(?:(?<x>.)(?<x>.))+/; print "x#1 is $-{x}[0], x#2 is $-{x}[1]"'
x#1 is a, x#2 is r
tchrist
  • 78,834
  • 30
  • 123
  • 180
-1

I'm not sure that is exactly what you're looking for, but the following code should do the trick.

$_='xaxbxc';
@l = /x(?<foo>.)/g;

print join(", ", @l)."\n";

But, I'm not sure this would work with overlapping strings.

kriss
  • 23,497
  • 17
  • 97
  • 116