5

所以我的 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)全局/不区分大小写的修饰符),如果这实际上是最简单的方法吗?

谢谢你的帮助 -

4

3 回答 3

10

问题 #1

由于各个正则表达式似乎没有多少结构共享,因此没有比仅列出您所做的命令更简单或更清晰的方法。在这样的代码中减少重复的一种常见方法是$text进入$_, 这样就不必说:

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

你可以说:

s/foo/bar/g;

这样做的一个常见习惯用法是使用退化for()循环作为主题化器:

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

此块的范围将保留 的任何预先存在$_的值,因此无需显式地localize $_

至此,您已经消除了几乎所有非样板字符——即使在理论上,它还能缩短多少?

除非你真正想要的(正如你的问题 #2 所暗示的)是改进的模块化,例如,迭代、报告、计数等所有正则表达式的能力。

问题 #2

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

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

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

1.eval()在你的foreach循环中使用。 这将使您能够保留当前的%rxcheck2哈希值。缺点:您应该始终关注 stringeval()的安全性。

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}($_);
}

您当然可以使用散列代替一些更有用的键作为散列,和/或您可以使用多值元素(或散列值),包括注释或其他信息。

于 2009-05-09T16:56:29.753 回答
4

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

#!/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;
于 2009-05-09T16:47:57.960 回答
4

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

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

于 2009-05-09T17:09:37.993 回答