如何将正则表达式中的变量插值延迟到使用点?

时间:2014-02-13 16:32:49

标签: regex perl

例如,让我们假设我有一组变量和一组插入这些变量的正则表达式:

my ($var1, $var2, $var3);
my @search_regexes=(
  qr/foo $var1/,
  qr/foo bar $var2/,
  qr/foo bar baz $var3/,
);

上述代码会向我们发出警告,告诉我们$var1$var2$var3未在$search_regexes中的正则表达式的正则表达式编译时定义。但是,我想在这些正则表达式中延迟变量插值,直到实际使用它们为止(或者一旦变量具有值,稍后(重新)编译):

# Later on we assign a value to $var1 and search for the first regex in $_ ...
$var1='Hello';
if (/$search_regexes[0]/)
{
  # Do something ...
}

如何在初始代码示例中重构构造以实现此目的?

作为奖励,我希望在将值分配给该正则表达式中出现的相应变量之后编译每个正则表达式,其方式与qr//运算符现在正在执行的方式相同(但为时太早) 。如果您可以展示如何进一步扩展解决方案以实现这一点,我将非常感激。

更新

我已经确定了Hunter方法的一个变种,因为使用它我不会受到性能影响,并且对现有代码的修改很少。其他答案也教会了我很多关于这个问题的替代解决方案及其在需要匹配很多行时的性能影响。我的代码现在类似于以下内容:

my ($var1, $var2, $var3);
my @search_regexes=(
  sub {qr/foo $var1/},
  sub {qr/foo bar $var2/},
  sub {qr/foo bar baz $var3/},
);

...
($var1,$var2,$var3)=qw(Hello there Mr);

my $search_regex=$search_regexes[$based_on_something]->();

while (<>)
{
  if (/$search_regex/)
  {
    # Do something ...
    # and sometimes change $search_regex to be another from the array
  }

}

这让我得到了我正在寻找的代码,只需对我的代码进行微小的更改(例如,只是在顶部添加了数组)并且每个正则表达式的使用都没有达到性能。

5 个答案:

答案 0 :(得分:11)

最好的解决方案是推迟正则表达式的编译,直到定义了这些变量。但首先是一个值得怀疑的解决方案:正则表达式可以包含代码:qr/foo (??{ $var1 })/。在匹配期间执行该块,然后将块的结果用作模式。

我们如何推迟编辑?

  1. 只需在分配变量时指定它们即可。这可能是您可能认为的问题,因为任何程序都可以在没有(重新)分配变量的情况下表达。坚持任何声明也必须是作业的规则(反之亦然),这应该有效。这样:

    my $var1;
    my $re = qr/$var1/;
    $var1 = ...;
    $bar =~ $re;
    

    变为:

    my $var1 = ...;
    $re = qr/$var1/;
    $bar =~ $re;
    
  2. 如果无法做到这一点,我们可能希望在匹配之前使用我们评估的闭包:

    my $var1;
    my $deferred_re = sub { qr/$var1/ };
    $var1 = ...;
    $bar =~ $deferred_re->();
    

    当然这会在每次调用时重新编译正则表达式。

  3. 我们可以通过缓存正则表达式来扩展之前的想法:

    package DeferredRegexp;
    use overload 'qr' => sub {
      my ($self) = @_;
      return $self->[0] //= $self->[1]->();
    };
    
    sub new {
       my ($class, $callback) = @_;
       return bless [undef, $callback] => $class;
    }
    

    然后:

    my $var1;
    my $deferred_re = DeferredRegexp->new(sub{ qr/$var1/ });
    $var1 = ...;
    $bar =~ $deferred_re;
    

答案 1 :(得分:8)

我认为如果你将每个正则表达式包装在匿名子中,你可以做这种推迟:

my ($var1, $var2, $var3);
my @search_regexes=(
  sub { return qr/foo $var1/         },
  sub { return qr/foo bar $var2/     },
  sub { return qr/foo bar baz $var3/ },
);

然后,当你要评估它们时,你只需“呼叫”匿名子:

($var1, $var2, $var3) = qw(thunk this code);
if( $_ =~ $search_regexes[0]->() ) {
   # Do something
}

我知道在Scheme中这叫做thunking我不确定它是否在Perl中有一个名字。您可以使用Proc objects

在Ruby中执行类似的操作

答案 2 :(得分:3)

(??{ })完全符合您的要求。

our $var1;
my $re = qr/foo (??{ $var1 )/;
...
local $var1 = ...;
/$re/

但那很尴尬。原始字符串是所谓的模板。有许多模板系统可以使它更清洁。

my $pat_template = 'foo [% var1 %]';
...
Template->new->process($pat_template, { var1 => ... }, \my $pat);
/$pat/

如果模板不需要存储在文件中,则可以使用构建器子。

my $re_gen = sub { my ($var1) = @_; qr/foo $var1/ };
...
my $re = $re_gen->(...);
/$re/

注意:在(??{ })内部,您可以使用在外部声明的词法变量来解决问题。这就是我在第一个片段中使用包变量的原因。

答案 3 :(得分:2)

Amon's answer是最完整的。但问题是,如果你不是100%确定它们应该是什么,你为什么要预编译你的正则表达式呢?

与任何编译一样,编译时必须解决所有问题。正如amon向您展示的那样,您可以使用变量声明正则表达式,但是当您再次调用它们时,它将重新编译正则表达式。

我怀疑你并不担心编译时间是简单的重用。如果你一遍又一遍地使用这些正则表达式,那么只有一个地方可以维护它们不是更好吗?

嗯,这听起来像是一个子程序:

sub test_regex {
    my $test_val  = shift;
    my $regex_val = shift;
    my $regex_num = shift;

    if ( not defined $regex_num     # Need both parameters
       die qq(Invalid call to subroutine test_regex);
    }

    if    ( $regex_num == 0 ) {
       return $test_val =~ /foo $regex_val/;
    }
    elsif ( $regex_num == 1 ) {
       return $test_val =~ /foo bar $regex_val/;
    }
    elsif ( $regex_num == 2 ) {
       return $test_val =~ /foo bar bas $regex_val/;
    }
    else {
       die qq(Invalid value for regular expression value);
    }
}

现在,您可以像这样致电test_regex

if ( test_regex ( $_, $var1, 1 ) ) {
    say "This is a regular expression match!";
}
else {
    say "No it didn't match";
}

你有一个点,你必须维护你的正则表达式(在你的子程序中),但你仍然可以一次又一次地调用它们。请注意,我必须传递三个参数:我正在测试的内容(可能是$_,但可能不是),$var1的值和子例程编号。

可能在我的子程序中使用了全局值,但这通常是一个坏主意:

sub test_regex {
   my regex_num = shift;   # Only thing I need. I'm assuming `$_` and `$var1` are global

    if ( not defined $regex_num     # Need both parameters
       die qq(Invalid call to subroutine test_regex);
    }

    if    ( $regex_num == 0 ) {
       return /foo $val1/;
    }
    elsif ( $regex_num == 1 ) {
       return /foo bar $val1/;
    }
    elsif ( $regex_num == 2 ) {
       return $test_val =~ /foo bar bas $val1/;
    }
    else {
       die qq(Invalid value for regular expression value);
    }
}

然后呼叫将是:

$val1 = 'fubar'
if ( test_regex( 1 ) ) {
    ....
}

它更符合您的要求,但这不是一个好主意。

答案 4 :(得分:-2)

如果这些只是字符串,请尝试用单引号括起来:

my ($var1, $var2, $var3);
my @search_regexes=(
  'foo $var1',
  'foo bar $var2',
  'foo bar baz $var3',
);

这肯定会在定义数组时保持变量不被插值,但是我不能保证在实际使用它们时它们会进行插值。试试吧。