Chinaunix首页 | 论坛 | 博客
  • 博客访问: 311342
  • 博文数量: 48
  • 博客积分: 4510
  • 博客等级: 中校
  • 技术积分: 556
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-05 18:19
文章分类

全部博文(48)

文章存档

2012年(1)

2011年(9)

2010年(1)

2009年(12)

2008年(25)

分类:

2008-12-28 23:01:46

Perl 笔记之 Perl 中的陷阱

我在使用 Perl 的过程中落入了一些陷阱,这是在爬出陷阱后的一些笔记,主要是提醒自己。当然也希望能给其他人提个醒,让他们能绕过这些陷阱,而不是落入后再辛苦的爬出来。

BTW,这些陷阱并不是按我落入的先后顺序整理


1. 用 each 遍历 hash

先看看 eacho 的文档, perldoc -f each

When the hash is entirely read, a null array is returned in list context (which when assigned produces a false (0) value), and "undef" in scalar context.  The next call to "each" after that will start iterating again.  There is a single iterator for each hash, shared by all "each", "keys", and "values" function calls in the program; it can be reset by reading all the elements from the hash, or by evaluating "keys HASH" or "values HASH".  If you add or delete elements of a hash while you're iterating over it, you may get entries skipped or duplicated, so don't.  Exception: It is always safe to delete the item most recently returned by "each()".

如果你和我一样喜欢围绕复杂数据结构组织程序的代码,那么很有可能落入这个陷阱:多次遍历 hash.
看个简单的例子:

use strict;
use warnings;

use Data::Dumper;

my %hash = ( foo => 'foo', bar => 'bar' );

while (my ($key, $val) = each %hash) {
    $hash{$key} = $val . "_val";
    print Data::Dumper->Dump([\%hash], [qw(*hash)]);
}


执行下你就会发现,这是个死循环。原因就是在 while 循环中 %hash 又被遍历了一次。当然,对于这个简单的例子,问题很好理解也很好解决。但是对于更复杂的程序来说,就不那么容易了。

另外,在这里更恰当的方式是使用 foreach + keys 遍历 %hash,不过效果是一样的,为了配合小标题,我就用了 each ;)

又掉进去一次 纪录下场景

OUTER:
while (my $line = <$filehandle>) {
        INNER:
        while (my ($key, $val) = each %hash) {
                if ($line =~ m/$val/) {
                        # do something

                        next OUTER;
                }
        }
}


上面这段代码在执行时会表现出各种各样乍看十分诡异的行为...

2. my $x = val if ...

嗯,也许你会说,“我不会这么定义变量”,但是相信我,在某一天,你精神不太好,而且又写了几个小时代码,更糟的是还调了几个小时代码,边调试边修改代码,那么有可能,仅仅是有可能,你会写下 my $x = val if ... 这种语句,然后第二天(甚或当天晚上)再多花几个小时找到这个 bug. 我这么说是因为...我就这么干过

看看文档是怎么说的 perldoc perlsyn
NOTE: The behaviour of a "my" statement modified with a statement modifier conditional or loop construct (e.g. "my $x if ...") is undefined.  The value of the "my" variable may be "undef", any previously assigned value, or possibly anything else.  Don't rely on it.  Future versions of perl might do something different from the version of perl you try it out on.  Here be dragons.
既然明确说了这么做行为未定义,那么也没什么好抱怨的,平常还是早点睡吧

3. 用局部变量覆盖全局变量

例如

use strict;
use warnings;

use Data::Dumper;

show_env(VAR => 'var');

sub show_env {
    my @ENV = @_;
    print Data::Dumper->Dump([\@ENV], [qw(*ENV)]);
};


执行下. Wow, 我的环境变量哪去了?

如果你习惯于用 use strict; + use warnings; (这是好习惯 ),那么你也一定习惯了用 my,那么保不齐你哪天就会写下 my @ENV = ...(就和我那天一样)。

4. 在 for my $a (@array) 中,$a 是 @array 成员的别名
很简单的一个测试脚本:

#!/usr/bin/perl

use strict;
use warnings;

my @str = qw(0001 000a);
for my $s (@str) {
    $s =~ s/^0+//;
    print $s, "\n";
}

for my $s (@str) {
    $s =~ s/^0+(?=\d)//;
    print $s, "\n";
}


执行下:
$ ./t.pl
1
a
1
a
这个结果显然不对,除非我对第二个正则表达式的理解有问题,可我查了文档的呀,而且这个也不复杂。
百思不得其解的情况下,我在第二个 for 之前打印了一下 @str 的值,如下:

#!/usr/bin/perl

use strict;
use warnings;

my @str = qw(0001 000a);
for my $s (@str) {
    $s =~ s/^0+//;
    print $s, "\n";
}

print "XXX: @str\n";
for my $s (@str) {
    $s =~ s/^0+(?=\d)//;
    print $s, "\n";
}

突然,我意识到了问题所在,是我早已知道的“在 for my $a (@array) 中,$a 是 @array 成员的别名”,也就是本节的标题。既然知道了问题所在,那么修改也就很容易了:

#!/usr/bin/perl

use strict;
use warnings;

my @str = qw(0001 000a);
for (@str) {
    (my $s = $_) =~ s/^0+//;
    print $s, "\n";
}

for (@str) {
    (my $s = $_) =~ s/^0+(?=\d)//;
    print $s, "\n";
}


看来,知道是一回事情,能不能避免又是另外一回事情

另外,这个问题 perlsyn 中讲的很详细,建议看看(perldoc perlsyn 然后搜索 foreach)。

5. system 对命令可能存在两次插值

将问题简化下,假设要通过 system 执行 Shell 命令输出字符串 '$PATH', 那么要如何写这个命令呢?

第一次尝试:

$ perl -e 'system("echo \$PATH")'
/usr/bin:/bin:/usr/sbin:/sbin


似乎是转义没有效果,打印 system 执行的命令看看:

$ perl -e 'print "echo \$PATH\n"'
echo $PATH


显然这个命令如果被 Shell 执行,那么输出肯定就是当前 $PATH 的值,也即前面看到的结果,为了防止 Shell 的插值,要再次转义:

perl -e 'system("echo \\\$PATH")'
$PATH


看看此时 Shell 执行的命令:

$ perl -e 'print "echo \\\$PATH\n"'
echo \$PATH


system 在碰到要执行的命令中包含 Shell 的元字符,如 $,的时候,就会调用 /bin/sh -c 执行该命令,这时 /bin/sh 会对命令再做一次插值,详见 perldoc -f system.


未完待续!
阅读(1814) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2009-01-09 00:25:56

说说我在使用中碰到问题就是觉得它不好了?你可知道 Perl 文档中专门有一个,perltrap,用来描述其他语言的程序员转到 Perl 后可能碰到的种种陷阱。 再说,用它、觉得它好,就应该觉得它是完美的?就不能说(我认为的)它的缺点了?

chinaunix网友2009-01-04 16:18:32

觉得perl不好,楼主可以不用啊