How to print only the capture groups of all matches in perl?

Viewed 606

How to print only the capture groups of all matches in perl? /g doesn't seem to work.

I don't think I'm doing any of it correctly, using if statements, this is why am asking. How is the proper way to do it? (I can't find anything on the Internet that helps and I am struggling for hours to make it finally work.)

$LONG_REGEX_WITH_TWO_CAPTURING_GROUPS="";
$file1="file1.html";

/* This part is complicated, this is why I said nothing 
 * about the two, but here is the result:
 *
 * Basically $2 (a letter) + whitespace + $1 (a filename)
 * a file.txt
 * b anotherfile.txt
 * c 3rdfile.txt
 * d 4thfile.txt
 * 
 * I want it to become>
 * a - (A specific part of the text in file.txt)
 * b - (A specific part of the text in anotherfile.txt)
 * etc.
*/

my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };

if ( $content1 =~ /$LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) {
    # Print the letter first ($2).
    print "$2 - ";
    # Open the corresponding file (it's name is $1).
    my $content2 = do { open my $fh, '<', $1 or die $!; local $/; <$fh>; };
    # Try to complete the task.
    if ( $content2 =~ /$SECOND_REGEX/g ) {
        print "$1\n"; # There is just one capturing group.
    }
}

However, this only prints the first match, even if it has a global flag.

As in:

a - The desired text.

Nevermind the code, the question is very simple: How to print only the content from the capture groups, but from all of the matches (or making it so it matches everything in the file)?

Thank you!

I'm editing so I can put the code here:

#!/usr/bin/perl

$file1="file1.html";
my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };

foreach ( $content1 =~ m/LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) {
    # If I were to put a print "$content1"; here, the program would have
    # no output. Here is the problem, the question still remains.
    print "$2 - ";
    my $content2 = do { open my $fh, '<', $1 or die $!; local $/; <$fh>; };
    foreach ( $content2 =~ m/SECOND_REGEX>/g ) {
        print "$1\n"; # There is just one capturing group.
    }
}

This worked for me:

#!/usr/bin/perl

$file1="file1.html";
my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };
while ( $content1 =~ /LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) 
    print "$2 - ";
    my $content2 = do { open my $fh, '<', "../../VT/$1" or die $!; local $/; <$fh>; };
    while ( $content2 =~ /SECOND_REGEX/g ) {
        print "$1\n\n<br/>"; # There is just one capturing group.
    }
}
2 Answers

You want to iterate over the matches, but you have no loop. The g modifier makes you an array of all matches and you have to iterate over the array.

@matches = ( 'foo' =~ m{o}g );

This would make you an array with two "o" strings in it.

You can iterate over the matches with code like this:

foreach ( 'foo' =~ m{o}g ) { ... }

If You want to iterate and need only the matching group, you must call the matching operator in scalar context. Here is an example how to do it:

$str="a m7 bcd 9 m2 cde m3";
while ($str =~ m{m(\d)}g) {
   print "$1\n";
}

This prints 7, 2 and 3, each in a line.

It is not clear to me how exactly that regex is meant to work but here are two possible situations.

It seems that you have a regex that matches multiple (two) subpatterns within one large pattern. Then you don't need the /g modifier; when the large pattern matches the subpatterns are matched as well (capture as needed). Then you can have the m operator in list context so that it returns these captures, instead of returning true/false, what it does in the scalar context

my $string = q(73 name);

my @matches = $string =~ /([0-9]+) \s* ([a-z]+)/xi;

if (@matches) {
    # it matched, process the two captures
}

This can be done inside the condition of the if statement

if (my @matches = $string =~ /([0-9]+)\s*([a-z]+)/i) { 
    # getting here only means that there were *some* matches
    # check @matches as suitable, process
}

Now this whole thing is scoped to the if statement; there is no @matches variable outside.

Or, in this case you can simply use the capture variables, like

if ( $string =~ /([0-9]+) \s* ([a-z]+)/xi ) {
    # use $1 and $2 (check whether they were both defined)
}

See more on regex operators in perlop, and look over the reference perlre.


Another possibility is that a regex pattern need be matched multiple times in the string, as the engine goes along the string and parses it. For that you indeed need the \g modifier.

It says in "Global matching" in perlretut

[...] The modifier /g stands for global matching and allows the matching operator to match within a string as many times as possible. In scalar context, successive invocations against a string will have /g jump from match to match, keeping track of position in the string as it goes along. [...]

Since you need both matches at the same time for processing you need to match in the list context and capture the returned matches into an array, then process that array. For example

my $string = '1 one 2 two';
my @matches = $string =~ /([a-z]+)/gi;  # @matches has elements: ('one', 'two')
# check how many @matches, etc

or perhaps have it inside if like above

if (my @matches = $string =~ /([a-z]+/gi) { 
    # check, process...
}

Add suitable checks for what was caught in @matches before processing it.


Comments on the code posted in Q

When you have the regex as the condition for an if statement it is in the "scalar" context. This means, as the quote from the docs above shows, that it will return matches one by one -- if invoked repeatedly. In your if it runs once, so you only get the first match. So $2 is undef.

When you have it under foreach (in the Edit), then it is indeed in the list condition -- but foreach obtains a list from the statement (so both matches) and then it iterates through it. So every time through you only have one of the matches on hand! Again no good.

Related