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

时间:2008-09-24 03:00:42

标签: regex perl

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

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

    $txt =~ $pattern;
}

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

当然,这不起作用。我试着评估替换:

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

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

9 个答案:

答案 0 :(得分:32)

  

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

传递
你呢?为什么不传递代码参考?例如:

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

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

通常,当你想将“做某事的东西”传递给子程序时(在你的问题的情况下是“正则表达式替换”),答案是传递对一段代码的引用。 Higher Order Perl是一本关于这个主题的好书。

答案 1 :(得分:8)

好吧,您可以使用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");
}

答案 2 :(得分:8)

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 // term而不是$ 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: <$1>\n";
            s/$r2//;
        }
        print "Residue 2: <$_>\n";
    }
    else
    {
        print "No match:\n";
    }
    print "Next?\n";
}

我们可以争论使用像$ r1这样的名字等等。但它完成了这项工作......它不是,而不是生产代码。

答案 3 :(得分:5)

eval "$txt =~ $pattern";
这变为
eval "\"foo baz\" =~ s/foo/bar/"
,替换不适用于文字字符串。

这样可行:

eval "\$txt =~ $pattern"
但这并不是很令人愉快。 eval几乎不是正确的解决方案。

zigdon的解决方案可以做任何事情,如果替换字符串是静态的,Jonathan的解决方案非常合适。如果你想要比第一个更有条理的东西,比第二个更灵活,我会建议混合:

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

答案 4 :(得分:5)

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

我不喜欢这个eval,它非常脆弱,有很多边框。

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

相同的效果
s/(\w+)/\u\L$1/g;

你可以打电话

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

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

确定API后,接下来就可以实施:

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$1" }, 'g');

结果:

  

内容类型

这对我来说很好。

答案 5 :(得分:4)

也许你可能会重新思考你的方法。

您希望将函数传递给正则表达式替换,可能是因为该函数将从其他源(从文件,套接字等读取)派生要操作的文本。但是你正在将正则表达式与正则表达式替换混为一谈。

在表达式s/foo/bar/中,您实际上有一个正则表达式(“/ foo /”)和一个替换(“bar”),它应该替换表达式匹配的内容。在您迄今为止尝试过的方法中,您在尝试使用eval时遇到了问题,主要是因为表达式中特殊字符可能会干扰eval或进行插值(即吞噬) )在评估过程中。

所以相反,尝试传递你的例程两个参数:表达式和替换:

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...
}

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

apply_regex('foo', 'bar');

或者,如果是,您可以使用qr//引用运算符创建一个正则表达式对象并将其作为第一个参数传递:

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

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

兰迪

答案 6 :(得分:0)

我有一个非常简单的大规模文件重命名脚本,它使用了这个技巧:

#!/opt/local/bin/perl
sub oops { die "Usage : sednames s/old/new [files ..]\n"; }
oops if ($#ARGV < 0);

$regex = eval 'sub { $_ = $_[0]; ' . shift(@ARGV) . '; return $_; }';
sub regex_rename { foreach (<$_[0]>) {
    rename("$_", &$regex($_));
} }

if ($#ARGV < 0) {  regex_rename("*");  }
else {  regex_rename(@ARGV);  }

可以使用任何修改$_ s/old/new的perl命令来修改文件。

我决定使用eval,这样正则表达式只需要编译一次。由于eval$_使我无法使用,因此存在一些不满:

eval 'sub { ' . shift(@ARGV) . ' }';

虽然&$regex肯定会修改$_;在致电"$_"之前要求$_评估rename。是的,eval非常脆弱,就像其他人一样。

答案 7 :(得分:0)

我发现了一种更好的方法:

sub proc {
    my ($pattern, $replacement) = @_;
    my $txt = "foo baz";

    $txt =~ s/$pattern/$replacement/g;  # This substitution is global.
}

my $pattern = qr/foo/;  # qr means the regex is pre-compiled.
my $replacement = 'bar';

proc($pattern, $replacement);

如果替换的标志必须是可变的,您可以使用:

sub proc {
    my ($pattern, $replacement, $flags) = @_;
    my $txt = "foo baz";

    eval('$txt =~ s/$pattern/$replacement/' . $flags);
}

proc(qr/foo/, 'bar', 'g');

请注意,您不需要在替换字符串中转义/

答案 8 :(得分:-1)

你是对的 - 你非常接近:

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