我需要将正则表达式替换作为变量传递:
sub proc {
my $pattern = shift;
my $txt = "foo baz";
$txt =~ $pattern;
}
my $pattern = 's/foo/bar/';
proc($pattern);
当然,这不起作用。我试着评估替换:
eval("$txt =~ $pattern;");
但这也不起作用。我在这里错过了多么可怕的事情?
答案 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;");