有没有更好的方法来保存 Perl 中正则表达式的替换部分?

Is there better way to save the substitution part of regular expression in Perl?

$search_32bit  = '(80 71 C3 (\S{8}) (77 55 66))';
$search_32bit =~ s/\s+//g;
$replace_32bit = 'A0 B0 C0 ';
$replace_32bit =~ s/\s+//g;

        @repls_32 = (
                [ $search_32bit, $replace_32bit],
                );

$hex = "9090908071C312345678775566000000777777";

foreach my $r (@repls_32) {

        $hex_tmp = $hex;
        (my $s_sign, my $mat_pos) = eval "$hex =~ s/$r->[0]/$r->[1]/i;return ($1, $-[0])";
        $len = length($s_sign);
        $replaced_str = substr($hex, $mat_pos, $len);
        print "matched_str: $s_sign\n";
        print "mat_pos: $mat_pos\n";
        print "length: $len\n";
        print "replaced_str: $replaced_str\n";
}

输出如下:

matched_str: 8071C312345678775566
mat_pos: 6
length: 20
replaced_str: A0B0C012345678775566

我的问题:

是否有更好的方法来保存正则表达式的替换部分(即 $replaced_str: A0B0C012345678775566)?

一种获取替换字符串的方法,大概是作为正则表达式动态构建的 运行s

my $repl_str;
$str =~ s{$pattern}{ $repl_str = code-to-build-replacement }e;

具体示例:用句点填充捕获

my $str = 'funny';

my $repl_str;
$str =~ s{ (.{3}) }{ $repl_str = '.' .  . '.' }ex;

say $repl_str;  #--> .fun.
say $str;       #--> .fun.ny

重点是替换部分里面的赋值表达式returns也是一样(赋值),所以替换字符串对regex还是可用的;这样做不会破坏事情。虽然现在有另一个变量浮动,但我认为没有用于替换字符串的内置正则表达式变量。

根据替换,仅通过添加该分配并不总是有效;回想一下 /e 的替代方是 code。因此,上面“正常”替换(没有 /e)中的 .. 必须调整为有效代码,其中 '.' 字符串连接到 [=23= 的捕获] 运算符,然后我们可以添加赋值和 运行 that.

我希望你能将它改编成那个 eval,为此我不知道你为什么需要它——除了也许 运行 从外部提供的代码,作为细绳?即便如此,我希望只提供部分正则表达式,您仍然可以编写正则表达式并可以执行上述操作。

澄清问题中的一些要点会有所帮助,但希望这对目前的情况有用。 (如果可能,将来考虑为您的需要构建一个简单明了的示例。)


在这种特定情况下,那些 </code>(等)似乎是捕获组,这是一个复杂的问题。然后他们需要 <code> (等),并且需要更多的工作来使用变量

一种方法是根据给定的 $replace 字符串构建实际的替换字符串

use warnings;
use strict;
use feature 'say';

my $str = shift // '9090908071C312345678558765432166aaaabbbb7700abc' ; 
say $str; 

my $search  = qr/8071C3(\S{8})55(\S{8})66(\S{8})77/; 
my $replace = q(A0B0C0567);

my $repl_str;
$str =~ s/$search/$repl_str = build_repl($replace, , , )/e; 
# or use @{^CAPTURE} instead of ,, with perl-5.27.2 or newer

say $str; 
say $repl_str;

sub build_repl {
    my ($r, @captures) = @_; 

    my @terms = grep { $_ } split /($\d)/, $r; 

    # Terms that start with $ are $N's and are picked from input
    my $str = join '', map { /^$/ ? shift(@captures) : $_ } @terms;
    return $str;
}

请参阅下面的讨论。 这会打印出

9090908071C312345678558765432166aaaabbbb7700abc
909090A0B0C012345678558765432166aaaabbbb7700abc
A0B0C012345678558765432166aaaabbbb77

(这是正确的,虽然很难看清。为什么一个问题中的示例这么长且难以阅读?)

我使用 qr to build a regex pattern, but '' (or q()) works here as well. Since 5.27.2 there is a builtin @{^CAPTURE} variable 所以也许可以使用它来代替显式的 ,,... 列表。

一个类似的例子有更多的解释可以在 。当然,还有其他方法可以组织代码来构建替换字符串,并且还有一些有用的库。

上面的 sub 进行了捕获,因此手头有它们,可以编写替换字符串。 /e 是必需的,以便 运行 正则表达式中的子,一旦捕获可用。

链接的答案构建 code 并使用 /ee。请仔细阅读相关警告并点击链接。我看不出这里需要那个。


这是评论中对上面是否可以简化的讨论

替换端的任何变量,包括 $N,都将被评估并与任何文字字符串连接成替换字符串。

my $str = 'bc';
$str =~ s/(.)/a/;  #--> abc

但是当替换对象在变量中时,就不一样了。有

$r = "a";  # Interpreter doesn't know what "" is

$str =~ s/(.)/a/;  #--> Use of uninitialized value ...

如果我们尝试使用单引号,那么这就是我们得到的,仅仅是文字字符 a$1

$r = 'a';  # now it won't try to evaluate  ...

$str =~ s/(.)/a/;  #--> ac  (but it doesn't do it here either)

那么我们如何让它把</code>放在替换端<em>并且</em>把它当作一个变量来评估呢?</p> <p>这就是 <code>/e 修饰符的用途,我们变量中的字符串现在必须具有有效代码的语法。然而,这仍然不足以获得 </code> 评估;整个工作需要 <code>/ee

$r = q('a'.);

$str =~ s/(.)/a/ee;  #--> abc  (that /ee is very unsafe)

但这有点复杂,给定的$r(问题中的$replace)必须处理成有效的代码语法,我们正在打开一个巨大的安全漏洞,因为/ee 将任何字符串评估为代码,然后在该字符串中评估代码。 (点击文本中链接的 post 中的链接。)

那么为什么不直接使用我们传递给 $Ns 的函数,它经过很好的评估,因此该函数可以获得实际捕获的字符串。然后在函数中我们可以解析 $r 并构建我们的替换字符串。

那么 build_repl 的作用是 所需要的。 (这就是为什么有它的库。)


尝试这些事情的一种方法是使用像

这样的“单行”
perl -wE'$_ = q(bc); $r = q(a); s/(.)/$r/; say'

perl -wE'$_ = q(bc); $r = q("a".); s/(.)/$r/ee; say'

q(...) 与单引号相同,而 qq(...) 与双引号相同。

首先,让我们修复您的错误。

use String::Substitution qw( sub_modify );

my $search  = qr/(80 71 C3 (\S{8}) (77 55 66))/xi;
my $replace = 'A0 B0 C0 ' =~ s/\s+//gr;          #  should be  in replacement.

sub_modify($hex, $search, $replace);                 # Fix code injection bugs

这相当于

use String::Substitution qw( sub_modify interpolate_match_vars );

my $search  = qr/(80 71 C3 (\S{8}) (77 55 66))/xi;
my $replace = 'A0 B0 C0 ' =~ s/\s+//gr;

sub_modify($hex, $search, sub { interpolate_match_vars($replace, @_) });

现在,只是保存字符串的问题了。

use String::Substitution qw( sub_modify interpolate_match_vars );
 
my $search  = qr/(80 71 C3 (\S{8}) (77 55 66))/xi;
my $replace = 'A0 B0 C0 ' =~ s/\s+//gr;

my $replacement_str;
sub_modify($hex, $search, sub { $replacement_str = interpolate_match_vars($replace, @_) });

不过,以上内容给人一种由内而外的感觉。可以如下翻转:

use String::Substitution qw( interpolate_match_vars last_match_vars );
 
my $search  = qr/(80 71 C3 (\S{8}) (77 55 66))/xi;
my $replace = 'A0 B0 C0 ' =~ s/\s+//gr;

my $replacement_str;
if ($hex =~ $search) {
   $replacement_str =
      substr($hex, $-[0], $+[0] - $-[0]) =
         interpolate_match_vars($replace, last_match_vars());
}

这也使得保存原始匹配变得容易。

use String::Substitution qw( interpolate_match_vars last_match_vars );
 
my $search  = qr/(80 71 C3 (\S{8}) (77 55 66))/xi;
my $replace = 'A0 B0 C0 ' =~ s/\s+//gr;

my $match_str;
my $replacement_str;
if ($hex =~ $search) {
   my $match_ref = \substr($hex, $-[0], $+[0] - $-[0]);
   $match_str = $$match_ref;
   $replacement_str = interpolate_match_vars($replace, last_match_vars());
   $$match_ref = $replacement_str;
}