将正则表达式替换作为变量传递给Perl?

时间:2020-03-06 14:38:24  来源:igfitidea点击:

我需要将正则表达式替换作为变量传递:

sub proc {
    my $pattern = shift;
    my $txt = "foo baz";

    $txt =~ $pattern;
}

my $pattern = 's/foo/bar/';
proc($pattern);

当然,这是行不通的。我尝试评估替代:

eval("$txt =~ $pattern;");

但这也不起作用。我在这里错过了什么可怕的明显事情?

解决方案

好了,我们可以使用qr //运算符对RE进行预编译。但是我们不能传递运算符(s ///)。

$pattern = qr/foo/;

print "match!\n" if $text =~ $pattern;

但是,如果必须传递替换运算符,则只能传递代码或者字符串:

proc('$text =~ s/foo/bar');

sub proc { 
   my $code = shift;

   ...

   eval $code;
}

或者,代码:

proc(sub {my $text = shift;  $text =~ s/foo/bar});

sub proc {
   my $code = shift;

   ...

   $code->("some text");
}

sub proc {
    my($match, $subst) = @_;
    my $txt = "foo baz";
    $txt =~ s/$match/$subst/;
    print "$txt\n";
}

my $matcher = qr/foo/;
my $sub_str = "bar";

proc($matcher, $sub_str);

而是直接回答问题。我们可以做更多的工作,但是当我使用qr //术语而不是$ sub_str作为简单文字时,则替换了扩展的正则表达式。

我最近需要为具有某些特殊SQL类型的语句创建一个解析器(测试解析器),识别这样的行,将其分为三个类型名称:

input: datetime year to second,decimal(16,6), integer

我用来演示此脚本的脚本使用带引号的正则表达式。

#!/bin/perl -w
use strict;
while (<>)
{
    chomp;
    print "Read: <$_>\n";
    my($r1) = qr%^input\s*:\s*%i;
    if ($_ =~ $r1)
    {
        print "Found input:\n";
        s%$r1%%;
        print "Residue: <$_>\n";
        my($r3) = qr%(?:year|month|day|hour|minute|second|fraction(?:\([1-5]\))?)%;
        my($r2) = qr%
                        (?:\s*,?\s*)?   # Commas and spaces
                        (
                            (?:money|numeric|decimal)(?:\(\d+(?:,\d+)?\))?   |
                            int(?:eger)?  |
                            smallint      |
                            datetime\s+$r3\s+to\s+$r3
                        )
                    %ix;
        while ($_ =~ m/$r2/)
        {
            print "Got type: <>\n";
            s/$r2//;
        }
        print "Residue 2: <$_>\n";
    }
    else
    {
        print "No match:\n";
    }
    print "Next?\n";
}

我们可以争论使用诸如$ r1之类的名称。但这确实完成了工作……它不是(也不是)生产代码。

I need to pass a regex substitution as a variable

你?为什么不通过代码引用?例子:

sub modify
{
  my($text, $code) = @_;
  $code->($text);
  return $text;
}

my $new_text = modify('foo baz', sub { $_[0] =~ s/foo/bar/ });

通常,当我们要将"某事做某事"传递给子例程(对于问题为"正则表达式替换")时,答案是将引用传递给一段代码。高阶Perl是一本关于该主题的好书。

eval "$txt =~ $pattern";

这变成

eval "\"foo baz\" =~ s/foo/bar/"

并且替换不适用于文字字符串。

也许我们可能会重新考虑方法。

我们想将一个正则表达式替换传递给一个函数,可能是因为该函数将从其他来源(从文件,套接字等读取)中派生要对其进行操作的文本。但是,我们正在将正则表达式与正则表达式替换混合在一起。

在表达式s / foo / bar /中,实际上我们有一个正则表达式(" / foo /")和一个替换项(" bar"),应替换该表达式所匹配的内容。到目前为止,我们尝试使用的方法都遇到了尝试使用" eval"的问题,这主要是因为表达式中特殊字符可能会干扰" eval"或者在过程中被内插(即被吞噬)评价。

因此,请尝试传递例程两个参数:表达式和替换:

eval "$txt =~ $pattern"

这种方法还有一个好处:如果正则表达式模式中没有任何特殊字符,则可以直接将其传递:

sub proc {
    my $pattern = shift;
    my $code = shift;
    my $txt = "foo baz";
    $txt =~ s/$pattern/$code->()/e;
    print "$txt\n";
}
my $pattern = qr/foo/;
proc($pattern, sub { "bar" });   # ==> bar baz
proc($pattern, sub { "\U$&" });  # ==> FOO baz

或者,如果可以,我们可以使用qr //quoting-operator来创建一个正则表达式对象并将其作为第一个参数传递:

sub apply_regex {
    my $regex = shift;
    my $subst = shift || ''; # No subst string will mean matches are "deleted"

    # some setup and processing happens...

    # time to make use of the regex that was passed in:
    while (defined($_ = <$some_filehandle>)) {
        s/$regex/$subst/g; # You can decide if you want to use /g etc.
    }

    # rest of processing...
}

最重要的是,我们确实不需要'eval'或者使用代码引用/闭包来完成此任务。这只会增加复杂性,使调试变得比实际需要的难度更大。

兰迪

s ///不是正则表达式。因此,我们不能将其作为正则表达式传递。

我不喜欢eval,因为它非常脆弱,带有很多边框。

我认为最好采用一种类似于Javascript的方法:传递一个正则表达式(在Perl中,即" qr //")和一个用于替代的代码参考。例如,传递参数可获得与以下效果相同的效果

apply_regex('foo', 'bar');

你可以打电话

apply_regex(qr{(foo|bar)}, 'baz');
apply_regex(qr/[ab]+/, '(one or more of "a" or "b")');
apply_regex(qr|\d+|); # Delete any sequences of digits

注意,'g'修饰符实际上不是正则表达式的标志(我认为将其添加到正则表达式是Javascript中的设计错误),因此我选择将其传递给第3个参数。

一旦确定了API,就可以下一步进行实现:

s/(\w+)/\u\L/g;

让我们尝试一下:

replace($string, qr/(\w+)/, sub { "\u\L" }, 'g');

结果:

sub replace {
    my($string, $find, $replace, $global) = @_;
    unless($global) {
        $string =~ s($find){ $replace->() }e;
    } else {
        $string =~ s($find){ $replace->() }ge;
    }
    return $string;
}

这对我来说很好。

我们说得对,我们非常接近:

print replace('content-TYPE', qr/(\w+)/, sub { "\u\L" }, 'g');

代码数量不匹配

代码数量不匹配