如何在同一个字符串上有效地处理多个Perl搜索/替换操作?

时间:2009-05-09 16:05:13

标签: regex perl hash substitution

所以我的Perl脚本基本上是一个字符串,然后通过多次搜索并替换它来尝试清理它,如下所示:

$text =~ s/<[^>]+>/ /g;
$text =~ s/\s+/ /g;
$text =~ s/[\(\{\[]\d+[\(\{\[]/ /g;
$text =~ s/\s+[<>]+\s+/\. /g;
$text =~ s/\s+/ /g;
$text =~ s/\.*\s*[\*|\#]+\s*([A-Z\"])/\. $1/g; # replace . **** Begin or . #### Begin or ) *The 
$text =~ s/\.\s*\([^\)]*\) ([A-Z])/\. $1/g; # . (blah blah) S... => . S...

正如你所看到的,我正在处理令人讨厌的HTML并且必须将其击败。

我希望有一种更简单,美观的方式来做到这一点。我有大约50行,看起来就像上面那样。

我通过使用哈希来解决了这个问题的一个版本,其中键是注释,哈希是reg表达式,如下所示:

%rxcheck = (
        'time of day'=>'\d+:\d+', 
    'starts with capital letters then a capital word'=>'^([A-Z]+\s)+[A-Z][a-z]',
    'ends with a single capital letter'=>'\b[A-Z]\.'
}

这就是我使用它的方式:

 foreach my $key (keys %rxcheck) {
if($snippet =~ /$rxcheck{ $key }/g){ blah blah  }
 }

当我尝试使用哈希表示密钥是表达式时,问题就出现了,它指向我要用它替换它的东西......并且它有1美元或2美元。

%rxcheck2 = (
        '(\w) \"'=>'$1\"'
}

以上是这样做的:

$snippet =~ s/(\w) \"/$1\"/g;

但我似乎无法将“$ 1”部分传递到正则表达式字面上(我认为这是正确的单词......即使我使用'标记,似乎正在解释$ 1。)因此,这导致:

if($snippet =~ /$key/$rxcheck2{ $key }/g){  }

这不起作用。

所以有2个问题:

简单:如何以一种易于编辑的方式处理大量的正则表达式,这样我就可以更改和添加它们而不必仅仅剪切和粘贴线条?

更难:我如何使用哈希(或数组,如果我有,我想要包括多个部分,如1)部分搜索,2)替换3)评论,4)全局/不区分大小写修饰符),如果这实际上是最简单的方法吗?

感谢您的帮助 -

3 个答案:

答案 0 :(得分:10)

问题#1

由于单个正则表达式似乎没有多少共享结构,所以实际上并没有比仅仅列出命令更简单或更清晰的方式。减少代码重复的一种常见方法是将$text移到$_,这样就不必说:

$text =~ s/foo/bar/g;

你可以说:

s/foo/bar/g;

这样做的一个常见习惯是使用简并for()循环作为局部化器:

for($text)
{
  s/foo/bar/g;
  s/qux/meh/g;
  ...
}

此块的范围将保留$_的任何预先存在的值,因此无需明确local ize $_

此时,你已经消除了几乎所有非样板人物 - 即使在理论上它也能缩短多少?

除非你真正想要的(正如你的问题#2所暗示的)改进了模块性,例如,能够迭代,报告,计算所有正则表达式。

问题#2

您可以使用qr//语法引用替换的“搜索”部分:

my $search = qr/(<[^>]+>)/;
$str =~ s/$search/foo,$1,bar/;

但是,我不知道如何充分引用“替换”部分。我曾希望qr//也适用于此,但事实并非如此。有两种选择值得考虑:

<强> 1。在eval()循环中使用foreach。这可以让您保留当前的%rxcheck2哈希值。缺点:你应该始终关注字符串eval()的安全性。

<强> 2。使用一组匿名子程序:

my @replacements = (
    sub { $_[0] =~ s/<[^>]+>/ /g; },
    sub { $_[0] =~ s/\s+/ /g; },
    sub { $_[0] =~ s/[\(\{\[]\d+[\(\{\[]/ /g; },
    sub { $_[0] =~ s/\s+[<>]+\s+/\. /g },
    sub { $_[0] =~ s/\s+/ /g; },
    sub { $_[0] =~ s/\.*\s*[\*|\#]+\s*([A-Z\"])/\. $1/g; },
    sub { $_[0] =~ s/\.\s*\([^\)]*\) ([A-Z])/\. $1/g; }
);

# Assume your data is in $_
foreach my $repl (@replacements) {
    &{$repl}($_);
}

当然,你可以使用哈希代替一些更有用的密钥作为哈希,和/或你可以使用包含注释或其他信息的多值元素(或哈希值)。

答案 1 :(得分:4)

哈希不好,因为它们是无序的。我找到一个数组数组,其第二个数组包含一个编译的正则表达式和一个字符串到eval(实际上它是一个双eval)效果最好:

#!/usr/bin/perl

use strict;
use warnings;

my @replace = (
    [ qr/(bar)/ => '"<$1>"' ],
    [ qr/foo/   => '"bar"'  ],
);

my $s = "foo bar baz foo bar baz";

for my $replace (@replace) {
    $s =~ s/$replace->[0]/$replace->[1]/gee;
}

print "$s\n";

我认为j_random_hacker的第二个解决方案远远优于我的。单个子程序为您提供最大的灵活性,比我的/ee解决方案快一个数量级:

bar <bar> baz bar <bar> baz
bar <bar> baz bar <bar> baz
         Rate refs subs
refs  10288/s   -- -91%
subs 111348/s 982%   --

以下是产生这些数字的代码:

#!/usr/bin/perl

use strict;
use warnings;

use Benchmark;

my @subs = (
    sub { $_[0] =~ s/(bar)/<$1>/g },
    sub { $_[0] =~ s/foo/bar/g },
);

my @refs = (
    [ qr/(bar)/ => '"<$1>"' ],
    [ qr/foo/   => '"bar"'  ],
);

my %subs = (
    subs => sub {
        my $s = "foo bar baz foo bar baz";
        for my $sub (@subs) {
            $sub->($s);
        }
        return $s;
    },
    refs => sub {
        my $s = "foo bar baz foo bar baz";
        for my $ref (@refs) {
            $s =~ s/$ref->[0]/$ref->[1]/gee;
        }
        return $s;
    }
);

for my $sub (keys %subs) {
    print $subs{$sub}(), "\n";
}

Benchmark::cmpthese -1, \%subs;

答案 2 :(得分:4)

你说你正在处理HTML。你现在意识到,与短暂而脆弱的解决方案相比,这几乎是一场失败的战斗。

正确的HTML解析器可以让您的生活更轻松。 HTML::Parser可能很难使用,但CPAN上还有其他非常有用的库,如果你可以指定你正在尝试做什么而不是如何