3

我们最近在我们的一个 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

4 回答 4

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 是第一个有修复的生产版本。

于 2011-12-29T21:01:11.773 回答
4

这显然是一个错误。检查最新版本,如果仍然存在问题,请按以下方式提交错误报告:

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

于 2011-12-29T20:21:30.267 回答
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如果您分配给新变量而不是在同一语句中使用and ,则不会出现此问题$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
于 2011-12-30T06:25:23.853 回答
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 所说,这是一个错误。

于 2011-12-29T20:25:32.157 回答