在Perl中动态捕获正则表达式匹配

时间:2016-12-01 14:36:26

标签: regex perl

我试图在Perl中动态捕获正则表达式匹配。我知道eval会帮助我做到这一点,但我可能做错了。

代码:

use strict;
use warnings;

my %testHash = (
    '(\d+)\/(\d+)\/(\d+)'   =>  '$1$2$3'
);

my $str = '1/12/2016';

foreach my $pattern (keys (%testHash)) {
    my $value = $testHash{$pattern};
    my $result;

    eval {
        local $_ = $str;
        /$pattern/;
        print "\$1 - $1\n";
        print "\$2 - $2\n";
        print "\$3 - $3\n";
        eval { print "$value\n"; }
    }
}

是否也可以将捕获的正则表达式模式存储在数组中?

5 个答案:

答案 0 :(得分:3)

我相信你真正想要的是以下动态版本:

say $str =~ s/(\d+)\/(\d+)\/(\d+)/$1$2$3/gr;

here提供了实现这一目标所需的条件。

use String::Substitution qw( gsub_copy );

for my $pattern (keys(%testHash)) {
   my $replacement = $testHash{$pattern};
   say gsub_copy($str, $pattern, $replacement);
}

请注意,$replacement也可以是回调。这允许更复杂的替换。例如,如果您想将1/12/2016转换为2016-01-12,则可以使用以下内容:

'(\d+)/(\d+)/(\d+)' => sub { sprintf "%d-%02d-%02d", @_[3,1,2] },

回答你的实际问题:

use String::Substitution qw( interpolate_match_vars last_match_vars );

for my $pattern (keys(%testHash)) {
   my $template = $testHash{$pattern};

   $str =~ $pattern   # Or /$pattern/ if you prefer
      or die("No match!\n");

   say interpolate_match_vars($template, last_match_vars());
}

答案 1 :(得分:3)

我不完全确定你想在这里做什么,但我认为你的计划不会像你认为的那样。

您正在使用eval代码块。这就像try块一样。如果它die块内eval,它将捕获该错误。它不会像代码一样运行你的字符串。你需要一个字符串eval

不是解释,而是替代方案。

此程序使用sprintf and numbers the parameters。模式中的%1$s语法表示_take第一个参数(1$)并将其格式化为字符串(%s)。您不需要本地化或分配给$_来进行匹配。 =~运算符会为您执行其他变量。我还使用qr{}来创建一个引用的正则表达式(本质上是一个包含预编译模式的变量),我可以直接使用它。由于{}作为分隔符,我不需要逃避斜杠。

use strict;
use warnings;
use feature 'say'; # like print ..., "\n"

my %testHash = (
    qr{(\d+)/(\d+)/(\d+)}         => '%1$s.%2$s.%3$s',
    qr{(\d+)/(\d+)/(\d+) nomatch} => '%1$s.%2$s.%3$s',
    qr{(\d+)/(\d+)/(\d\d\d\d)}    => '%3$4d-%2$02d-%1$02d',
    qr{\d}                        => '%s', # no capture group
);

my $str = '1/12/2016';

foreach my $pattern ( keys %testHash ) {
    my @captures = ( $str =~ $pattern );

    say "pattern: $pattern";

    if ($#+ == 0) {
        say "  no capture groups";
        next;
    }

    unless (@captures) {
        say "  no match";
        next;
    }

    # debug-output
    for my $i ( 1 .. $#- ) {
        say sprintf "  \$%d - %s", $i, $captures[ $i - 1 ];
    }

    say sprintf $testHash{$pattern}, @captures;
}

我包括四个例子:

  • 第一种模式是你拥有的模式。如上所述,它使用%1$s等等。
  • 第二个不匹配。我们通过在标量上下文中查看它来检查@captured中的元素数量。
  • 第三个显示您还可以重新排序结果,甚至使用sprintf格式。
  • 最后一个没有捕获组。我们通过查看最后一个元素的索引来检查($#作为通常具有@ sigil)in @+的数组的sigil,其中保存了结尾的偏移量当前活动动态范围中最后成功的子匹配。第一个元素是整体匹配的结束,所以如果这只有一个元素,我们就没有捕获组。

我的输出是:

pattern: (?^:(\d+)/(\d+)/(\d\d\d\d))
  $1 - 1
  $2 - 12
  $3 - 2016
2016-12-01
pattern: (?^:(\d+)/(\d+)/(\d+) nomatch)
  no match
pattern: (?^:\d)
  no capture groups
pattern: (?^:(\d+)/(\d+)/(\d+))
  $1 - 1
  $2 - 12
  $3 - 2016
1.12.2016

请注意,输出中的顺序是混合的。这是因为哈希没有在Perl中排序,如果你在没有sort的哈希中迭代键,则顺序是随机的。

答案 2 :(得分:0)

道歉!我意识到我的问题和示例代码都含糊不清。但在阅读了你的建议后,我得到了以下代码。 我还没有对此代码进行优化,并且替换存在限制。

foreach my $key (keys %testHash) {

    if ( $str =~ $key ) {
        my @matchArr = ($str =~ $key); # Capture all matches

        # Search and replace (limited from $1 to $9)
        for ( my $i = 0; $i < @matchArr; $i++ ) {
            my $num = $i+1;
            $testHash{$key} =~ s/\$$num/$matchArr[$i]/;
        }

        $result = $testHash{$key};

        last;
    }
}

print "$result\n";

答案 3 :(得分:-1)

  

是否也可以将捕获的正则表达式模式存储在数组中?

当然可以将捕获的子串存储在一个数组中:

#!/usr/bin/env perl

use strict;
use warnings;

my @patterns = map qr{$_}, qw{
    (\d+)/(\d+)/(\d+)
};

my $str = '1/12/2016';

foreach my $pattern ( @patterns ) {
    my @captured = ($str =~ $pattern)
        or next;
    print "'$_'\n" for @captured;
}

输出:

'1'
'12'
'2016'

我不太明白您在代码中使用localeval EXPReval BLOCK的组合以及以下哈希的目的要做什么:

my %testHash = (
    '(\d+)\/(\d+)\/(\d+)'   =>  '$1$2$3'
);

如果你试图编纂这个模式应该导致三次捕获,你可以这样做:

my @tests = (
    {
        pattern => qr{(\d+)/(\d+)/(\d+)},
        ncaptures => 3,
    }
);

my $str = '1/12/2016';

foreach my $test ( @tests ) {
    my @captured = ($str =~ $test->{pattern})
        or next;
    unless (@captured == $test->{ncaptures}) {
        # handle failure
    }
}

请参阅states,了解如何自动计算模式中捕获组的数量。在答案中使用该技术:

#!/usr/bin/env perl

use strict;
use warnings;

use Test::More;

my @tests = map +{ pattern => qr{$_}, ncaptures => number_of_capturing_groups($_) }, qw(
    (\d+)/(\d+)/(\d+)
);

my $str = '1/12/2016';

foreach my $test ( @tests ) {
    my @captured = ($str =~ $test->{pattern});
    ok @captured == $test->{ncaptures};
}

done_testing;

sub number_of_capturing_groups {
    "" =~ /|$_[0]/;
    return $#+;
}

输出:

ok 1
1..1

答案 4 :(得分:-1)

评估列表上下文中的正则表达式返回匹配项。所以在你的例子中:

use Data::Dumper; # so we can see the result
foreach my $pattern (keys (%testHash)) {
    my @a = ($str =~/$pattern/);
    print Dumper(\@a);
}

可以胜任。

HTH 乔治