为什么Perl的m // g运算符有时会导致将NULL引入文本?

时间:2011-12-29 20:16:41

标签: regex perl side-effects

我们最近在一个Perl脚本中遇到了一些奇怪的结果,其中NULL字符(Pe​​rl中的\ 0)被引入到某些文本中。我们最终将其跟踪到偶然用于Perl m //匹配运算符的// g运算符。直到发生这种情况,我甚至都不知道你可以使用// g和m //运算符,因为我只使用它与s ///运算符。

无论如何,即使我们通过删除错误// g来修复错误,我也很想知道为什么这个小脚本在文本中引入了一个NULL字符! : - )

my $text = "01";

if ($text =~ m/(\d+)/g)
{
    $text = "A$1";
}

if ($text =~ m/\0/)
{
    print "Text contains NULL!\n";
}

阻止NULL出现的细微更改:如果我更改$ text的值(例如,更改为“0”或仅“1”或许多其他组合),则不再引入NULL。如果我将赋值值从“A $ 1”更改为“$ 1”,则不再引入NULL。如果我将“A $ 1”分配给完全不同的变量,则不会将NULL引入该变量。如果我在m //匹配期间删除了// g运算符,则不会引入NULL。

Perl大师可以解释一下这种行为吗?我用谷歌搜索找不到任何东西。

4 个答案:

答案 0 :(得分:5)

if ($text =~ m/(\d+)/g)

错了。具体来说,if (/.../g)形式的代码是错误的。它在概念上没有任何意义(“如果匹配,直到它不匹配”???)并且会产生不希望的结果。

$_ = "01ab";
if (/(\d+)/g) { say $1; }   # 01
if (/(.*)/g)  { say $1; }   # ab!!!

摆脱“g”。


字符串的结尾通常后跟NUL。

$ perl -MDevel::Peek -e'Dump "01"'
SV = PV(0x88b4740) at 0x88d1368
  REFCNT = 1
  FLAGS = (PADTMP,POK,READONLY,pPOK)
  PV = 0x88d52f0 "01"\0
  CUR = 2
  LEN = 12

当匹配的起始位置位于字符串的末尾时,您的Perl版本似乎有一个与NUL匹配的错误。没有插入NUL。幸运的是,如果你修复了你的错误代码,你将不会遇到这个错误。


../perl/Porting/bisect.pl           \
   --target=miniperl --expect-fail  \
   --start=v5.13.0 --end=v5.14.0    \
   -e'
      my $text = "01";
      if ($text =~ m/(\d+)/g) { $text = "A$1"; }
      exit($text =~ m/\0/ ? 1 : 0);
   '

显示它由6f1401dc2acd2a2b85df22b0a74e5f7e6e0a33aa修复。

基于git tag --contains 6f1401dc2acd2a2b85df22b0a74e5f7e6e0a33aa,5.13.2是第一个开发版本,5.14.0是第一个获得修复的生产版本。

答案 1 :(得分:4)

这显然是一个错误。检查最新版本,如果它仍然是一个问题,这里是如何提交错误报告:

http://perldoc.perl.org/perlbug.html

答案 2 :(得分:2)

有一个perl错误,但您也遇到了编程问题。除了设置后的立即语句外,不要依赖特殊变量的值。立即存储它们的值。

遇到这些问题时,请查看数据。事实证明这是一个奇怪的问题,看起来像处理捕获缓冲区的错误。

use v5.10;
use feature qw(unicode_strings);

my $text = "01";

if ($text =~ m/(\d+)/g)
{
    say "\$1 [$1]: ", join ' ', map { sprintf '%04X', ord } split //, $1;
    say 'Text: ', join ' ', map { sprintf '%04X', ord } split //, $text;

    $text = "A$1";
    say "\$1 [$1]: ", join ' ', map { sprintf '%04X', ord } split //, $1;
    say 'Text: ', join ' ', map { sprintf '%04X', ord } split //, $text;
}

在您真正想要使用$1构建新字符串以分配给同一个变量之前,所有内容都是正确的,此时值似乎消失了。请注意,在分配后,$1是不同的:

% perl5.12.2 test.pl
$1 [01]: 0030 0031
Text: 0030 0031
$1 [AA]: 0041 0041
Text: 0041 0041 0000

这也是一种奇怪的方式。 perl做一些棘手的处理以记住字符串中的偏移量。对于v5.14,$1仍然是字符串中的前两个字符:

% perl5.14.2 test.pl
$1 [01]: 0030 0031
Text: 0030 0031
$1 [A0]: 0041 0030
Text: 0041 0030 0031

如果您在同一语句中指定新变量而不是使用$test$1,那么这个问题就不会出现(这应该是完全正常的,但我们都知道“应该是什么” “通常意味着”。如果您立即捕获特殊变量的值,也不是问题:

use v5.10;
use feature qw(unicode_strings);

my $text = "01";

if ($text =~ m/(\d+)/g)
{
    my $one = $1;
    say "\$1 [$1]: ", join ' ', map { sprintf '%04X', ord } split //, $1;
    say 'Text: ', join ' ', map { sprintf '%04X', ord } split //, $text;

    $text = "A$one";
    say "\$1 [$1]: ", join ' ', map { sprintf '%04X', ord } split //, $1;
    say 'Text: ', join ' ', map { sprintf '%04X', ord } split //, $text;
}

现在,即使是v5.12也是正确的:

$ perl5.12.2 test.pl
$1 [01]: 0030 0031
Text: 0030 0031
$1 [A0]: 0041 0030
Text: 0041 0030 0031

答案 3 :(得分:0)

$ perl -e '$text = "01"; if ($text =~ m/(\d+)/g) { $text = "A$1"; }; print "$text\n"; print "Contains nul" if $text =~ m/\0/''
A01

(perl 5.12.4)

正如@Dan所说,这是一个错误。