perlfaq7 - Perl 语言的一般问题
版本 5.20210520
本部分处理 Perl 语言的一般问题,这些问题显然不属于任何其他部分。
没有 BNF,但是如果你特别勇敢,可以在源代码发行版中的 perly.y 中仔细查看 yacc 语法。语法依赖于非常智能的标记化代码,因此请做好冒险进入 toke.c 的准备。
用 Chaim Frenkel 的话来说:“Perl 的语法不能简化为 BNF。解析 perl 的工作在 yacc、词法分析器、障眼法和镜子之间分配。”
它们是类型说明符,详见 perldata
$ for scalar values (number, string or reference)
@ for arrays
% for hashes (associative arrays)
& for subroutines (aka functions, procedures, methods)
* for all types of that symbol name. In version 4 you used them like
pointers, but in modern perls you can just use references.
还有几个你可能会遇到的符号,它们并不是真正的类型说明符
<> are used for inputting a record from a filehandle.
\ takes a reference to something.
请注意,<FILE> 既不是 文件的类型说明符,也不是句柄的名称。它是应用于句柄 FILE 的 <>
运算符。它从句柄 FILE 中读取一行(好吧,记录——参见 "$/" in perlvar)在标量上下文中,或 所有 行在列表上下文中。在对文件执行 open、close 或 <>
以外的任何其他操作,甚至在讨论句柄时,不要 使用括号。以下内容是正确的:eof(FH)
、seek(FH, 0, 2)
和“从 STDIN 复制到 FILE”。
通常,裸字不需要加引号,但在大多数情况下可能应该加(并且必须在 use strict
下加)。但由一个简单单词和 =>
运算符的左操作数组成的哈希键都算作加了引号
This is like this
------------ ---------------
$foo{line} $foo{'line'}
bar => stuff 'bar' => stuff
块中的最后一个分号是可选的,列表中的最后一个逗号也是可选的。良好的风格(参见 perlstyle)建议在单行器之外添加它们
if ($whoops) { exit 1 }
my @nums = (1, 2, 3);
if ($whoops) {
exit 1;
}
my @lines = (
"There Beren came from mountains cold",
"And lost he wandered under leaves",
);
一种方法是将返回值视为列表并对其进行索引
$dir = (getpwnam($user))[7];
另一种方法是在左侧使用 undef 作为元素
($dev, $ino, undef, undef, $uid, $gid) = stat($file);
您还可以使用列表切片仅选择所需的元素
($dev, $ino, $uid, $gid) = ( stat($file) )[0,1,4,5];
如果您运行的是 Perl 5.6.0 或更高版本,则 use warnings
pragma 允许精细控制产生的警告。有关更多详细信息,请参见 perllexwarn。
{
no warnings; # temporarily turn off warnings
$x = $y + $z; # I know these might be undef
}
此外,您可以启用和禁用警告类别。您关闭要忽略的类别,仍然可以获得其他类别的警告。有关包括类别名称和层次结构在内的完整详细信息,请参见 perllexwarn。
{
no warnings 'uninitialized';
$x = $y + $z;
}
如果您使用的是较旧版本的 Perl,则 $^W
变量(在 perlvar 中记录)控制块的运行时警告
{
local $^W = 0; # temporarily turn off warnings
$x = $y + $z; # I know these might be undef
}
请注意,与所有标点变量一样,您当前不能对 $^W
使用 my(),只能使用 local()。
扩展是从 Perl 调用已编译 C 代码的一种方式。阅读 perlxstut 是了解有关扩展的更多信息的不错地方。
实际上,它们没有。Perl 复制的所有 C 运算符在 Perl 中的优先级与在 C 中的优先级相同。问题出在 C 没有的运算符上,特别是将列表上下文赋予其右侧所有内容的函数,例如 print、chmod、exec 等。此类函数称为“列表运算符”,并显示在 perlop 中的优先级表中。
一个常见的错误是编写
unlink $file || die "snafu";
这被解释为
unlink ($file || die "snafu");
为避免此问题,请添加额外的括号或使用超低优先级 or
运算符
(unlink $file) || die "snafu";
unlink $file or die "snafu";
“英语”运算符(and
、or
、xor
和 not
)的优先级故意低于列表运算符,以应对上述情况。
另一个优先级令人惊讶的运算符是指数运算。它的结合力甚至强于一元减号,使 -2**2
产生负四而不是正四。它还具有右结合性,这意味着 2**3**2
是 2 的 9 次方,而不是 8 的平方。
虽然 Perl 的 ?:
运算符具有与 C 中相同的优先级,但它会产生一个左值。这会将 $x 分配给 $if_true 或 $if_false,具体取决于 $maybe 的真假性
($maybe ? $if_true : $if_false) = $x;
通常情况下,您不会“声明”一个结构。只需使用(可能是匿名)哈希引用。有关详细信息,请参阅 perlref 和 perldsc。以下是一个示例
$person = {}; # new anonymous hash
$person->{AGE} = 24; # set field AGE to 24
$person->{NAME} = "Nat"; # set field NAME to "Nat"
如果您正在寻找更严格的东西,请尝试 perlootut。
perlnewmod 是一个不错的起点,如果您不想公开您的模块,请忽略有关上传到 CPAN 的部分。
ExtUtils::ModuleMaker 和 Module::Starter 也是不错的起点。现在,许多 CPAN 作者使用 Dist::Zilla 尽可能实现自动化。
有关模块的详细文档,请参阅:perlmod、perlmodlib、perlmodstyle。
如果您需要包含 C 代码或 C 库接口,请使用 h2xs。h2xs 将创建模块分发结构和初始接口文件。 perlxs 和 perlxstut 会对此进行详细说明。
请当前维护者让您成为共同维护者或将模块转让给您。
如果您由于某种原因无法联系到作者,请联系 [email protected] 的 PAUSE 管理员,他们可能能够提供帮助,但每种情况的处理方式各不相同。
如果您还没有 Perl 作者上传服务器 (PAUSE) 的登录名,请获取一个:http://pause.perl.org
写信至 [email protected],说明您为联系当前维护者所做的工作。PAUSE 管理员也会尝试联系维护者。
在流量较大的网站上发布公开消息,宣布您接管该模块的意图。
稍等片刻。PAUSE 管理员不想采取行动太快,以防当前维护者正在休假。如果没有对私人通信或公开帖子的回应,PAUSE 管理员可以将模块转让给您。
(由 brian d foy 贡献)
在 Perl 中,类只是一个包,方法只是一个子例程。Perl 不会比这更正式,并允许您按照自己喜欢的方式设置包(即,它不会为您设置任何内容)。
另请参阅 perlootut,这是一个涵盖类创建的教程,以及 perlobj。
您可以使用 CPAN 提供的 Scalar::Util 模块的 tainted() 函数(或从 Perl 5.8.0 版开始包含在 Perl 中)。另请参阅 "perlsec 中的清洗和检测已污染的数据"。
闭包在 perlref 中记录。
闭包 是一个计算机科学术语,具有精确但难以解释的含义。通常,闭包在 Perl 中实现为匿名子例程,它们对作用域之外的词法变量具有持久引用。这些词法神奇地引用子例程定义时存在的变量(深度绑定)。
闭包最常用于您可以让函数的返回值本身成为函数的编程语言中,就像您在 Perl 中一样。请注意,一些语言提供匿名函数,但无法提供适当的闭包:例如 Python 语言。有关闭包的更多信息,请查看任何有关函数式编程的教科书。Scheme 是一种不仅支持而且鼓励闭包的语言。
这是一个经典的非闭包函数生成函数
sub add_function_generator {
return sub { shift() + shift() };
}
my $add_sub = add_function_generator();
my $sum = $add_sub->(4,5); # $sum is 9 now.
add_function_generator() 返回的匿名子例程在技术上不是闭包,因为它不引用作用域之外的词法。使用闭包为您提供了一个函数模板,其中留出了一些自定义槽位以供以后填充。
将此与以下 make_adder() 函数进行对比,其中返回的匿名函数包含对该函数作用域之外的词法变量的引用。这样的引用要求 Perl 返回一个适当的闭包,从而永久锁定函数创建时词法的那个值。
sub make_adder {
my $addpiece = shift;
return sub { shift() + $addpiece };
}
my $f1 = make_adder(20);
my $f2 = make_adder(555);
现在$f1->($n)
始终是 20 加上您传递的任何 $n,而 $f2->($n)
始终是 555 加上您传递的任何 $n。闭包中的 $addpiece 仍然存在。
闭包通常用于不太深奥的目的。例如,当您想将一段代码传递到函数中时
my $line;
timeout( 30, sub { $line = <STDIN> } );
如果要执行的代码已作为字符串传递,'$line = <STDIN>'
,则假设的 timeout() 函数将无法访问其调用者作用域中的词法变量 $line。
闭包的另一个用途是使变量对命名的子例程私有,例如,在子例程创建时初始化的计数器,并且只能从子例程内部修改。这有时与包文件中的 BEGIN 块一起使用,以确保变量在包的生命周期内不会被干扰
BEGIN {
my $id = 0;
sub next_id { ++$id }
}
有关此内容的更详细讨论,请参阅 perlsub;请参阅持久私有变量条目。
此问题已在 perl 5.004_05 中修复,因此防止它的方法是升级您的 perl 版本。;)
变量自杀是指您(暂时或永久地)丢失变量值。它是由 my() 和 local() 与闭包或别名 foreach() 迭代器变量和子例程参数交互作用域引起的。以前很容易无意中以这种方式丢失变量值,但现在很难了。请看此代码
my $f = 'foo';
sub T {
while ($i++ < 3) { my $f = $f; $f .= "bar"; print $f, "\n" }
}
T;
print "Finally $f\n";
如果您遇到变量自杀,则子例程中的 my $f
不会获取值为 'foo'
的 $f
的新副本。输出显示在子例程中,$f
的值泄露了,而它不应该泄露,如下所示
foobar
foobarbar
foobarbarbar
Finally foo
向 $f
中添加三次“bar”应该是一个新的 $f
,my $f
应在每次循环中创建一个新的词法变量。预期的输出是
foobar
foobar
foobar
Finally foo
您需要传递对这些对象的引用。有关此特定问题,请参阅 perlsub 中的“按引用传递”,有关引用的信息,请参阅 perlref。
传递常规变量和函数非常容易:只需传递对现有或匿名变量或函数的引用即可
func( \$some_scalar );
func( \@some_array );
func( [ 1 .. 10 ] );
func( \%some_hash );
func( { this => 10, that => 20 } );
func( \&some_func );
func( sub { $_[0] ** $_[1] } );
从 Perl 5.6 开始,您可以使用标量变量表示文件句柄,将其视为任何其他标量。
open my $fh, $filename or die "Cannot open $filename! $!";
func( $fh );
sub func {
my $passed_fh = shift;
my $line = <$passed_fh>;
}
在 Perl 5.6 之前,您必须使用 *FH
或 \*FH
符号。这些是“类型全局”——有关更多信息,请参阅 perldata 中的“类型全局和文件句柄”,尤其是 perlsub 中的“按引用传递”。
以下是如何传递字符串和正则表达式以进行匹配的示例。您可以使用 qr//
运算符构建模式
sub compare {
my ($val1, $regex) = @_;
my $retval = $val1 =~ /$regex/;
return $retval;
}
$match = compare("old McDonald", qr/d.*D/i);
若要将对象方法传递到子例程,可以执行以下操作
call_a_lot(10, $some_obj, "methname")
sub call_a_lot {
my ($count, $widget, $trick) = @_;
for (my $i = 0; $i < $count; $i++) {
$widget->$trick();
}
}
或者,可以使用闭包来捆绑对象、其方法调用和参数
my $whatnot = sub { $some_obj->obfuscate(@args) };
func($whatnot);
sub func {
my $code = shift;
&$code();
}
你还可以调查 UNIVERSAL 类(标准 perl 分发的一部分)中的 can() 方法。
(由 brian d foy 贡献)
在 Perl 5.10 中,使用 state
声明变量。state
声明创建词法变量,该变量在对子例程的调用之间保持不变
sub counter { state $count = 1; $count++ }
你可以使用超出范围的词法变量来伪造静态变量。在此示例中,你定义了子例程 counter
,它使用词法变量 $count
。由于你将其包装在 BEGIN 块中,因此 $count
在编译时定义,但在 BEGIN 块的末尾也会超出范围。BEGIN 块还确保子例程及其使用的值在编译时定义,以便子例程可以像任何其他子例程一样随时使用,并且你可以将此代码放在与程序文本中的其他子例程相同的位置(即通常在代码的末尾)。子例程 counter
仍然引用数据,并且是你可以访问该值(并且每次执行此操作时都会增加该值)的唯一方式。由 $count
定义的内存块中的数据对 counter
是私有的。
BEGIN {
my $count = 1;
sub counter { $count++ }
}
my $start = counter();
.... # code that calls counter();
my $end = counter();
在前面的示例中,你创建了一个函数私有变量,因为只有一个函数记住了它的引用。你可以在变量处于作用域内时定义多个函数,并且每个函数都可以共享“私有”变量。它并不是真正的“静态”,因为你可以在词法变量处于作用域内时在函数外部访问它,甚至可以创建对它的引用。在此示例中,increment_count
和 return_count
共享该变量。一个函数添加到该值,另一个函数仅返回该值。它们都可以访问 $count
,并且由于它已经超出范围,因此没有其他方法可以访问它。
BEGIN {
my $count = 1;
sub increment_count { $count++ }
sub return_count { $count }
}
若要声明文件私有变量,你仍然使用词法变量。文件也是一个作用域,因此在文件中定义的词法变量无法从任何其他文件中看到。
请参阅 perlsub 中的“持久私有变量” 以获取更多信息。尽管我们在本答案中没有使用匿名子例程,但 perlref 中对闭包的讨论可能对你有所帮助。有关详细信息,请参阅 perlsub 中的“持久私有变量”。
local($x)
保存全局变量 $x
的旧值,并为子例程的持续时间分配一个新值,该值在从该子例程调用的其他函数中可见。这是在运行时完成的,因此称为动态作用域。local() 始终影响全局变量,也称为包变量或动态变量。
my($x)
创建一个仅在当前子例程中可见的新变量。这是在编译时完成的,因此称为词法或静态作用域。my() 始终影响私有变量,也称为词法变量或(不正确地)静态(作用域)变量。
例如
sub visible {
print "var has value $var\n";
}
sub dynamic {
local $var = 'local'; # new temporary value for the still-global
visible(); # variable called $var
}
sub lexical {
my $var = 'private'; # new private variable, $var
visible(); # (invisible outside of sub scope)
}
$var = 'global';
visible(); # prints global
dynamic(); # prints local
lexical(); # prints global
注意,在任何时候都不会打印值“private”。这是因为 $var 仅在 lexical() 函数的块中具有该值,并且对被调用的子例程隐藏了该值。
总之,local() 不会让您认为私有局部变量。它给全局变量一个临时值。如果您想要私有变量,那么 my() 就是您要找的。
请参阅 "perlsub 中的私有变量通过 my()" 和 "perlsub 中的临时值通过 local()" 以获取令人痛苦的详细信息。
如果您知道您的包,您可以明确地提及它,就像在 $Some_Pack::var 中一样。请注意,$::var 符号不是当前包中的动态 $var,而是“main”包中的符号,就像您写了 $main::var 一样。
use vars '$var';
local $var = "global";
my $var = "lexical";
print "lexical is $var\n";
print "global is $main::var\n";
或者,您可以使用编译器指令 our() 将动态变量引入当前词法范围。
require 5.006; # our() did not exist before 5.6
use vars '$var';
local $var = "global";
my $var = "lexical";
print "lexical is $var\n";
{
our $var;
print "global is $var\n";
}
在深度绑定中,匿名子例程中提到的词法变量与创建子例程时范围内的变量相同。在浅度绑定中,它们是与子例程调用时范围内的同名变量。Perl 始终使用词法变量的深度绑定(即,使用 my() 创建的变量)。但是,动态变量(又名全局、局部或包变量)实际上是浅度绑定的。考虑一下不要使用它们的另一个原因。请参阅 "什么是闭包?" 的答案。
my()
和 local()
为 =
的右侧提供列表上下文。<$fh> 读操作与 Perl 的许多函数和运算符一样,可以判断它在哪个上下文中被调用,并做出适当的行为。通常,scalar() 函数可以提供帮助。此函数本身不会对数据进行任何操作(与流行的神话相反),而是告诉其参数以其标量方式进行操作。如果该函数没有定义的标量行为,那么这当然帮不了你(例如使用 sort())。
但是,要在这个特定情况下强制标量上下文,你只需要省略括号
local($foo) = <$fh>; # WRONG
local($foo) = scalar(<$fh>); # ok
local $foo = <$fh>; # right
你可能应该使用词法变量,尽管这里的问题是一样的
my($foo) = <$fh>; # WRONG
my $foo = <$fh>; # right
你为什么要这么做? :-)
如果你想覆盖预定义函数,例如 open(),那么你必须从不同的模块导入新定义。请参阅 "perlsub 中的“覆盖内置函数”。
如果你想重载 Perl 运算符,例如 +
或 **
,那么你将需要使用 use overload
pragma,在 overload 中记录。
如果你讨论的是在父类中隐藏方法调用,请参阅 "perlootut 中的“覆盖方法和方法解析”。
(由 brian d foy 贡献)
以 &foo
调用子例程,没有尾随括号,会忽略 foo
的原型,并向其传递参数列表的当前值 @_
。这里有一个示例; bar
子例程调用 &foo
,它打印其参数列表
sub foo { print "Args in foo are: @_\n"; }
sub bar { &foo; }
bar( "a", "b", "c" );
当你使用参数调用 bar
时,你会看到 foo
获得相同的 @_
Args in foo are: a b c
使用尾随括号调用子例程,无论是否有参数,都不会使用当前 @_
。更改示例以在调用 foo
后放置括号会更改程序
sub foo { print "Args in foo are: @_\n"; }
sub bar { &foo(); }
bar( "a", "b", "c" );
现在输出显示 foo
没有从其调用者那里获取 @_
。
Args in foo are:
但是,在调用中使用 &
仍然会覆盖 foo
的原型(如果存在)
sub foo ($$$) { print "Args infoo are: @_\n"; }
sub bar_1 { &foo; }
sub bar_2 { &foo(); }
sub bar_3 { foo( $_[0], $_[1], $_[2] ); }
# sub bar_4 { foo(); }
# bar_4 doesn't compile: "Not enough arguments for main::foo at ..."
bar_1( "a", "b", "c" );
# Args in foo are: a b c
bar_2( "a", "b", "c" );
# Args in foo are:
bar_3( "a", "b", "c" );
# Args in foo are: a b c
@_
直通功能的主要用途是编写子例程,其主要工作是为你调用其他子例程。有关更多详细信息,请参阅 perlsub。
Perl 中有一个 given/when 语句,但它是实验性的,并且将来可能会发生变化。有关更多详细信息,请参阅 perlsyn。
一般的答案是使用 CPAN 模块,例如 Switch::Plain
use Switch::Plain;
sswitch($variable_holding_a_string) {
case 'first': { }
case 'second': { }
default: { }
}
或者对于更复杂的比较,if-elsif-else
for ($variable_to_test) {
if (/pat1/) { } # do something
elsif (/pat2/) { } # do something else
elsif (/pat3/) { } # do something else
else { } # default
}
这里有一个基于模式匹配的 switch 的简单示例,排列方式使其看起来更像 switch 语句。我们将根据存储在 $whatchamacallit 中的引用类型进行多路条件
SWITCH: for (ref $whatchamacallit) {
/^$/ && die "not a reference";
/SCALAR/ && do {
print_scalar($$ref);
last SWITCH;
};
/ARRAY/ && do {
print_array(@$ref);
last SWITCH;
};
/HASH/ && do {
print_hash(%$ref);
last SWITCH;
};
/CODE/ && do {
warn "can't print function ref";
last SWITCH;
};
# DEFAULT
warn "User defined type skipped";
}
请参阅 perlsyn 以了解此样式中的其他示例。
有时,您应该更改常量和变量的位置。例如,假设您想测试给出的许多答案中的哪一个,但以不区分大小写的方式进行测试,并且还允许使用缩写。如果您希望所有字符串都以不同的字符开头,或者您希望安排匹配项,以便一个优先于另一个,则可以使用以下技术,例如,此处 "SEND"
优先于 "STOP"
chomp($answer = <>);
if ("SEND" =~ /^\Q$answer/i) { print "Action is send\n" }
elsif ("STOP" =~ /^\Q$answer/i) { print "Action is stop\n" }
elsif ("ABORT" =~ /^\Q$answer/i) { print "Action is abort\n" }
elsif ("LIST" =~ /^\Q$answer/i) { print "Action is list\n" }
elsif ("EDIT" =~ /^\Q$answer/i) { print "Action is edit\n" }
一种完全不同的方法是创建函数引用的哈希。
my %commands = (
"happy" => \&joy,
"sad", => \&sullen,
"done" => sub { die "See ya!" },
"mad" => \&angry,
);
print "How are you? ";
chomp($string = <STDIN>);
if ($commands{$string}) {
$commands{$string}->();
} else {
print "No such command: $string\n";
}
从 Perl 5.8 开始,还可以使用源筛选器模块 Switch
来获取 switch 和 case。现在不鼓励使用它,因为它与 Perl 5.10 的本机 switch 不完全兼容,而且由于它是作为源筛选器实现的,因此在涉及复杂语法时,它并不总是按预期工作。
在 "Autoloading" in perlsub 中讨论的 AUTOLOAD 方法允许您捕获对未定义函数和方法的调用。
对于在 use warnings
下会触发警告的未定义变量,您可以将警告提升为错误。
use warnings FATAL => qw(uninitialized);
一些可能的原因:您的继承关系混乱,您拼错了方法名称,或者对象类型错误。查看 perlootut 了解上述任何情况的详细信息。您还可以使用 print ref($object)
来找出 $object
被赋予的类。
出现问题的另一个可能原因是,您在 Perl 看到存在这样的包之前,对类名使用了间接对象语法(例如,find Guru "Samy"
)。最明智的做法是确保在开始使用包之前定义所有包,如果您使用 use
语句而不是 require
,则会处理此问题。如果不是,请确保改用箭头符号(例如,Guru->find("Samy")
)。对象符号在 perlobj 中进行了说明。
务必阅读 perlmod 中有关创建模块的内容,以及 "Method Invocation" in perlobj 中有关间接对象的危险性。
(由 brian d foy 贡献)
要找出你当前所在的包,使用特殊文字 __PACKAGE__
,如 perldata 中所述。你只能将特殊文字用作单独的标记,因此不能像使用变量那样将它们内插到字符串中
my $current_package = __PACKAGE__;
print "I am in package $current_package\n";
如果你想找出调用你代码的包,也许是为了提供更好的诊断,如 Carp 所做的那样,请使用内置的 caller
sub foo {
my @args = ...;
my( $package, $filename, $line ) = caller;
print "I was called from package $package\n";
);
默认情况下,你的程序从包 main
开始,因此你将始终处于某个包中。
这与找出对象被祝福到的包不同,后者可能不是当前包。为此,请使用 Scalar::Util 中的 blessed
,它是 Perl 5.8 以来标准库的一部分
use Scalar::Util qw(blessed);
my $object_package = blessed( $object );
大多数情况下,你不应该关心对象被祝福到的包,只要它声称继承自该类即可
my $is_right_class = eval { $object->isa( $package ) }; # true or false
而且,在 Perl 5.10 及更高版本中,你不必检查继承关系即可查看对象是否可以处理角色。为此,你可以使用来自 UNIVERSAL
的 DOES
my $class_does_it = eval { $object->DOES( $role ) }; # true or false
你可以安全地用 DOES
替换 isa
(尽管反过来不成立)。
(由 brian d foy 贡献)
注释掉多行 Perl 的快速而肮脏的方法是用 Pod 指令包围这些行。你必须将这些指令放在行首和 Perl 期望新语句的位置(因此不在语句中间,如 #
注释)。你使用 =cut
结束注释,结束 Pod 部分
=pod
my $object = NotGonnaHappen->new();
ignored_sub();
$wont_be_assigned = 37;
=cut
快速而肮脏的方法仅在你没有计划在源代码中保留已注释代码时才有效。如果出现 Pod 解析器,你的多行注释将显示在 Pod 翻译中。一种更好的方法也可以将其隐藏在 Pod 解析器中。
=begin
指令可以标记一个部分以供特定目的使用。如果 Pod 解析器不想处理它,它就会忽略它。使用 comment
标记注释。使用具有相同标记的 =end
结束注释。你仍然需要 =cut
从 Pod 注释返回到 Perl 代码
=begin comment
my $object = NotGonnaHappen->new();
ignored_sub();
$wont_be_assigned = 37;
=end comment
=cut
有关 Pod 的更多信息,请查看 perlpod 和 perlpodspec。
使用马克-杰森·多米纳斯提供的此代码
sub scrub_package {
no strict 'refs';
my $pack = shift;
die "Shouldn't delete main package"
if $pack eq "" || $pack eq "main";
my $stash = *{$pack . '::'}{HASH};
my $name;
foreach $name (keys %$stash) {
my $fullname = $pack . '::' . $name;
# Get rid of everything with that name.
undef $$fullname;
undef @$fullname;
undef %$fullname;
undef &$fullname;
undef *$fullname;
}
}
或者,如果你正在使用最近发布的 Perl,则可以只使用 Symbol::delete_package() 函数。
初学者经常认为他们希望变量包含变量的名称。
$fred = 23;
$varname = "fred";
++$$varname; # $fred now 24
这有时可行,但出于两个原因,这是一个非常糟糕的主意。
第一个原因是此技术仅适用于全局变量。这意味着如果 $fred 是在上述示例中使用 my() 创建的词法变量,则代码根本不起作用:你将意外访问全局变量并完全跳过私有词法变量。全局变量很糟糕,因为它们很容易意外冲突,并且通常会生成不可扩展且令人困惑的代码。
在 use strict
pragma 下禁止符号引用。它们不是真正的引用,因此不会进行引用计数或垃圾回收。
使用变量来保存另一个变量名称的另一个原因是不好的,这是因为这个问题通常源于对 Perl 数据结构(特别是哈希)缺乏了解。通过使用符号引用,你只是使用包的符号表哈希(如 %main::
),而不是用户定义的哈希。解决方案是使用你自己的哈希或真正的引用。
$USER_VARS{"fred"} = 23;
my $varname = "fred";
$USER_VARS{$varname}++; # not $$varname++
在那里,我们使用 %USER_VARS 哈希而不是符号引用。有时,这会在使用变量引用从用户读取字符串并希望将其扩展到 Perl 程序变量的值时出现。这也是一个坏主意,因为它混淆了程序可寻址的命名空间和用户可寻址的命名空间。而不是读取字符串并将其扩展到程序自身变量的实际内容
$str = 'this has a $fred and $barney in it';
$str =~ s/(\$\w+)/$1/eeg; # need double eval
最好保留一个哈希,如 %USER_VARS,并让变量引用实际引用该哈希中的条目
$str =~ s/\$(\w+)/$USER_VARS{$1}/g; # no /e here at all
这比以前的方法更快、更简洁、更安全。当然,你不需要使用美元符号。你可以使用自己的方案来减少混淆,例如带括号的百分号符号等。
$str = 'this has a %fred% and %barney% in it';
$str =~ s/%(\w+)%/$USER_VARS{$1}/g; # no /e here at all
人们有时认为他们希望变量包含变量名称的另一个原因是他们不知道如何使用哈希构建适当的数据结构。例如,假设他们希望在程序中使用两个哈希:%fred 和 %barney,并且他们希望使用另一个标量变量按名称引用它们。
$name = "fred";
$$name{WIFE} = "wilma"; # set %fred
$name = "barney";
$$name{WIFE} = "betty"; # set %barney
这仍然是一个符号引用,并且仍然存在上面列出的问题。更好的做法是编写
$folks{"fred"}{WIFE} = "wilma";
$folks{"barney"}{WIFE} = "betty";
并从一开始就使用多级哈希。
你必须使用符号引用的唯一时间是当你真的必须引用符号表时。这可能是因为它是一些无法引用的事物,例如格式名称。对于方法调用,这样做也很重要,因为这些调用总是通过符号表进行解析。
在这些情况下,你可以暂时关闭 strict 'refs'
,以便你可以使用符号表。例如
@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
no strict 'refs'; # renege for the block
*$name = sub { "<FONT COLOR='$name'>@_</FONT>" };
}
所有这些函数(red()、blue()、green() 等)看起来是独立的,但闭包中的实际代码实际上只编译了一次。
因此,有时您可能希望使用符号引用直接操作符号表。对于格式、句柄和子例程,这并不重要,因为它们始终是全局的——您无法对它们使用 my()。不过,对于标量、数组和哈希——以及通常对于子例程——您可能只希望使用硬引用。
(由 brian d foy 贡献)
“bad interpreter”消息来自 shell,而不是 perl。实际消息可能因您的平台、shell 和区域设置而异。
如果您看到“bad interpreter - no such file or directory”,则 perl 脚本中的第一行(“shebang”行)不包含 perl(或任何其他能够运行脚本的程序)的正确路径。有时,当您将脚本从一台机器移动到另一台机器时,就会发生这种情况,并且每台机器都有不同的 perl 路径——例如 /usr/bin/perl 与 /usr/local/bin/perl。它还可能表示源机器具有 CRLF 行终止符,而目标机器仅具有 LF:shell 尝试查找 /usr/bin/perl<CR>,但找不到。
如果您看到“bad interpreter: Permission denied”,则需要使脚本可执行。
在任何一种情况下,您仍然应该能够使用 perl 显式运行脚本
% perl script.pl
如果您收到“perl: command not found”之类的消息,则 perl 不在您的 PATH 中,这也可能意味着 perl 的位置不在您期望的位置,因此您需要调整 shebang 行。
(由 Alex Beamish 提供)
如果 C 库的新版本与您要升级的版本 ABI 兼容(即应用程序二进制接口兼容),并且共享库版本没有更改,则无需重新编译。
版权所有 (c) 1997-2013 Tom Christiansen、Nathan Torkington 和其他注明作者。保留所有权利。
此文档是免费的;您可以在与 Perl 自身相同的条款下重新分发和/或修改它。
无论其分发如何,此文件中的所有代码示例特此置于公共领域。您被允许且鼓励在自己的程序中使用此代码以获取乐趣或利润,具体取决于您认为合适的方式。在代码中添加一个简单的评论以表示感谢将是礼貌的,但不是必需的。