perl常见问题集(一)

我如何作 (任何事)?
你到 CPAN(见 perlfa)找过了吗?也许别人已经写了某个模组可以解决你的 问题。你查过相关的说明文件了吗 (man pages)?以下是一份概要的索引:

物件 (Objects) perlref, perlmod, perlobj, perltie
资料结构 (Data Structures) perlref, perllol, perldsc
模组 (Modules) perlmod, perlmodlib, perlsub
正规表示法 (Regexps) perlre, perlfunc, perlop
升级至 Perl5 (Moving to perl5) perltrap, perl
与 C连结 (Linking w/C) perlxstut, perlxs, perlcall, perlguts, perlembed
杂项 (Various) http://www.perl.com/CPAN/doc/FMTEYEWTK/index.html
(不是说明文件,但还是很有用)

perltoc里有一份粗略的 perl 说明文件组的目录。


--------------------------------------------------------------------------------

如何以互动的方式使用 Perl?
典型的作法是使用 perldebug(1)说明文件里提到的 Perl 除虫器,在一个「空的」(译者:即不存在的)程式上执行,像这样:

perl -de 42

接下来所打入的任意合法 Perl程式码皆会立刻被评估。同时,你可以检查符号表 (symbol table)、取得堆叠的记录 (stack backtraces)、检视变数值、设定阻断点 (set breakpoints) 以及其他符号式除虫器 (symbolic debuggers) 所能作的动作。


--------------------------------------------------------------------------------

有 Perl shell吗?
基本上来说,没有。Shell.pm模组 (是 perl 标准套件之一)只是叫 perl 将非 Perl语言的命令当作 shell的命令来试着执行看看罢了。perl原始码套件中的 perlsh,功能简易,也很无趣,不过仍可能是你所要的。


--------------------------------------------------------------------------------

如何替我的 Perl程式除虫?
你用过 -w吗?

你试过 use strict吗?

你是否检查过每个系统呼叫 (system call)所传回的值?

读了 perltrap说明文件吗?

你试过 perldebug里所提到的 Perl除虫器吗?


--------------------------------------------------------------------------------

如何检测 (profile)我的 perl程式?
你该自 CPAN抓取 Devel: Prof 模组,并且使用 perl 标准套件所附的 Benchmark.pm。 Benchmark.pm让你测量程式码的某部份在执行上所花的时间,而 Devel: Prof则详细地替你分析哪一部份的程式用掉多少时间。


--------------------------------------------------------------------------------

如何替我的 Perl程式作交叉参考 (cross-reference)?
随着新发行的 alpha版 Perl编译器(它不在一般标准套件里)而来的 B::Xref模组可 以替你的 Perl程式制作 cross-reference报告。用法是:

perl -MO=Xref[,OPTIONS] foo.pl


--------------------------------------------------------------------------------

有 Perl专用的美化列印程式 (pretty-printer)吗?
C有 indent(1)可以将原始码格式美化,但 Perl并没有能做得像它那麽好的东西。扫瞄器 (scanner) 和分析器 (parser) 间复杂的反馈 (feedback)(把 vgrind 和 emacs等程式搞混的就是这反馈)使得撰写一个独立的 Perl 分析器成了一项艰巨的挑战。

当然,若你直接照 perlstyle里面的指示写程式,就根本没有必要重新安排格式。

你所用的编辑器可以并也应能帮你把原始码的格式弄漂亮些。像 emacs的 perl-mode就能帮你把大部分 (但非全部)的程式码排列得漂亮些,而其它普通的编辑器也能提供一定程度的协助。

如果你试着使用 vgrind程式从雷射印表机印出漂亮的原始码,可以参考: http://www.perl.com/CPAN/doc/misc/tips/working.vgrind.entry ,但是碰到复杂的程式码可能就不能全然令人满意了。


--------------------------------------------------------------------------------

有 Perl的 ctags吗?
有个简单的在 http://www.perl.com/CPAN/authors/id/TOMC/scripts/ptags.gz 也许符合你的需要。


--------------------------------------------------------------------------------

哪里有 vi用的 Perl巨集?
在 ftp://ftp.perl.com/pub/vi/toms.exrc有完整的 Tom Christiansen之 vi设定档, 它是给 vi模拟器用的标准测试档 (standard benchmark file)。它与 nvi配合得最好,巧的是,这个出自 Berkeley的编辑器也可以内嵌一个 Perl直译器 --参看 http://www.perl.com/CPAN/src/misc。


--------------------------------------------------------------------------------

给 emacs用的 perl模式又要去哪抓呢?
从大约 Emacs 19.22版 (version 19 patchlevel 22)起,已内含了 perl-mode.el及 perl 除虫器的支援。它们应该会和标准的 Emacs 19版一起出货。

在 perl原始码的目录下,你会找到一个叫作 ``emacs'' 的目录,里面包括一个 cperl-mode 可以把程式中的关键字上色、提供内文相关的协助以及其它方便的功能。

注意:``main'foo''(其中的单引号)会让 emacs的 perl-mode生病,并且会弄乱内 缩 (indentation) 与精华 (hilighting)。不过你本来就该用 ``main::foo''的 (译者按: main'foo 是表示模组或 package的旧式写法;新式的 [perl5的]写法是 main::foo)。


--------------------------------------------------------------------------------

如何在 Perl里使用 curses?
CPAN里的 Curses模组提供了一个通往 curses 程式库的动态载入物件模组介面。


--------------------------------------------------------------------------------

X或 Tk如何与 Perl配合呢?
Tk这个完全以 Perl 为基础,物件导向化的介面,让你不用学 Tcl也可以使用 Tk工具组。Sx则是 Athena Widget set专用的介面。两者都可在 CPAN取得。


--------------------------------------------------------------------------------

如何不靠 CGI或 Tk之助作出简单的目录(选单)?
http://www.perl.com/CPAN/authors/id/SKUNZ/perlmenu.v4.0.tar.gz 是个以 curses为基础的模组,可以达成你的要求。


--------------------------------------------------------------------------------

我可以动态地将 C常式载入 Perl吗?
若你的系统架构有支援的话,标准 perl 套件便应该有此功能(介由 DynaLoader 这个模组)。详情请参看 perlxstut 。


--------------------------------------------------------------------------------

什麽是 undump?
看下个问题。


--------------------------------------------------------------------------------

如何让我的 Perl程式跑得更快些?
最好是能设计一个较好的演算法 (algorithm),这通常会让程式有大不相同的表现。 骆驼书第八章里有些你或许想知道的增进效率小技巧。

其它方法包括自动载入较少使用的 Perl 程式码。请参看标准 perl 套件中的 AutoSplit及 AutoLoader模组的用法。或当你能断定程式执行效率的瓶颈在何处时,用 C来写那个部份,就像用组合语言来撰写 C程式的瓶颈部份一样。与此法相近的是使用以 C撰写瓶 颈部份的模组 (例如 CPAN中的 PDL 模组)。

在某些情况下,使用後端的编译器把程式编译成位元码 (byte code)(可节省编译时间) 或是将 perl程式转编译为 C程式的作法值得一试;这些作法绝对会节省编译的时间并且有时能省一些[但不多]执行时间 。请参考“编译你的 Perl程式”这个问题的答案。

如果你目前是将你的 perl直译器动态连结到 libc.so的话,重新作一份静态连结到 libc.a的 perl直译器可以提高 10-25%的执行效能。虽然这会使你的 perl直译器变得更胖,但你的 Perl程式 (及程式设计者) 或许会因此而感谢你。详情请参考 perl标准套件原始码版本中的 INSTALL 档案。

一些未经证实的报告中宣称有些使用 sfio的 Perl直译器表现得比没有用 sfio的还好 (针对於 IO频繁的应用程式)。想试试看?参考 perl套件原始程式版中的 INSTALL 档案,尤其是 ``Selecting File IO mechanisms''这一段。

使用 undump程式把编译後的档案格式存到硬碟里以加快执行的速度已经是老掉牙的手法了。它已不再是个可行的方法,因为这方法只有几种平台能用,况且它终究不是个治本之 道。


--------------------------------------------------------------------------------

如何让我的 Perl程式吃少一点的记忆体?
当问题变成时间与空间的交易时, Perl 几乎总是用记忆体来帮忙解决问题。 Perl中的纯量 (Scalar) 耗掉的记忆体比 C中的字串形态还多,阵列又更多, 更别谈杂凑阵列了 (Hashes)。关於这一点,我们当然还有很多工作得作,近来发布的版本,已开始针对这些问题做改进了。例如, 5.004 版中, 重复的杂凑阵列索引值 (duplicate hash keys) 由使用它的杂凑阵列共用,这样就不用再重新定份位置给它了。

在某些情况下,使用 substr()或 vec()来模拟阵列有很大的好处。例如,一个有上千 个布林代数值的阵列将占用至少 20,000位元组的空间,但是它可以被转变为一个 125位元组的位元向量 (bit vector)以节省相当可观的记忆体。标准套件中的 Tie::SubstrHash模组也能够帮助特定形态的资料结构节省些记忆体。若你正在和一些特殊的资料结构奋战 (例如,矩阵),用 C写的模组所耗掉的记忆体可能低於同功能并用 Perl写的模组。

另一件值得一试的是,查一下你的 Perl是以系统内的 malloc 还是 Perl内含的 malloc 编译起来的。不论是哪个,试着换成另一个,再看看这是否造成任何差别。关於 malloc的资讯可在 perl标准套件原始码版中的 INSTALL 档案找到。键入 perl -V:usemymalloc就可以知道你是否在使用 perl的 malloc。


--------------------------------------------------------------------------------

把指标传回到区域资料是不安全的做法吗?
不,Perl的资源回收 (garbage collection)系统会解决此问题。

sub makeone {
my @a = ( 1 .. 10 );
return /@a;
}

for $i ( 1

print $many[4][5], "/n";

print "@many/n";


--------------------------------------------------------------------------------

我如何释放一个阵列或杂凑阵列以缩小我的程式尺寸?
你无法这麽作。系统配置给程式的记忆体是覆水难收。这也是为何执行很长一段时间的 程式有时会重新执行 (re-exec)它们自己的原因。

然而,在使用你的变数时,明智地用 my()来定义执行范围,可让 Perl在脱离该范围後 将它们所占的空间释放给其它部份的程式。 (注:my()的变数也比全域变数执行起来快 10%。)当然,一个全域变数永远没有超出范围的时候,所以你无法将它占用的空间自动重新分配,不过,把它 undef() 或/和 delete()会有相同的效果。总之,在 Perl里,你并不能/应该去担心太多有关记忆体定址与解除这件事,而我们连添加这项功能(资料形态的预先定址),目前都已在进行中。


--------------------------------------------------------------------------------

如何让我的 CGI脚本 (script)执行起来更有效率?
除了使一般 Perl程式加快或缩小的平常手段外,一个 CGI 程式还有其他的顾虑。也许它每秒会被执行好几次。每次它执行时,重新编译所花的时间、加上定址所需的 1 MB以上的系统记忆体,就是一个大杀手。光是编译成 C 是没啥帮助的 ,因为瓶颈在於整个程序开始时所负担的包袱 (start-up overhead) 。

最起码有两种较流行的方法可以避免这些包袱。一种解法是将 mod_perl 或是 mod_fastcgi其中一个模组加在你所执行的 Apache HTTP server (可从 http://www.apache.org/取得)。有了 mod_perl 和 Apache::*模组 (从 CPAN取得),httpd执行时会带起一个内 嵌的 Perl直译器,而它会预先编译你的程式,并在不产生其它子程序的情况下用同一个定址空间来执行。Apache 扩充模组亦给 Perl一个连通 server API 的管道,所以用 Perl写的模组可以做到任何 C写的模组所具备的功能。而有了 FCGI模组 (自 CPAN取得),你以 sfio (参看 perl标准套件原始码版本中的 INSTALL档案) 和 mod_fastcgi (从 http://www.fastcgi.com/取得)模组编译成的 Perl 直译器将使你的每个 perl程式变成一个固定的 CGI 背景程序 (daemon process)。

这些方法对你的系统与你撰写 CGI程式的方法都有超乎想像之外的影响,所以请小心地 探索它们。


--------------------------------------------------------------------------------

如何隐藏 Perl程式的原始码?
删除它。 说真的,有一些具有不同“安全”等级的方法(大部分都不能令人满意)。

然而,首先,你 不能拿走读取权,不然你的程式怎麽被解译或是编译呢? (不过那也并不表示一个 CGI程式的原始码可以被使用者读取。)所以你得让档案权限停留在 0755这个友善的阶段。

有些人认为这是个安全上的漏洞。不过若你的程式作的是不安全的事情,光仰赖别人 看不见这些漏洞、不知从何下手,那麽它依然是不安全的。其实对有些人来说他们并 不需要看见程式原始码便可能判定并揭露这些不安全的部份。透过隐瞒达到的安全, 就是不修正臭虫反而隐藏它们,实际上是没有安全性可言的。

你可以试着透过原始码过滤模组 (CPAN中的 Filter::*)来替原始码加密。但高手也许有 办法将其解密还原。你也可以用下面提到的 byte code 编译器与直译器,但高手也有可能反解译它。你可以试试後面提到的原生码编译器 (native-code compiler),但高手也有可 能反组译它。这些手段都需要不同难度的技巧才能让别人拿到你的原始码,但没有一种能 够很确定地隐藏它。(这对每种语言来说都为真,不是只有 Perl)

如果你所担心的是别人自你的程式码中获利,那麽一纸权限执照是能提供你法律上安全的唯一途径。注册你的软体并且写份权限说明,再加上一些具威胁性的句子像“这是 XYZ公司未出版的专有软体。你能撷取它并不代表你具有使用的权限...”之类云云。当然,我们不是律师,所以若你想要你的执照中每一句话在法庭上都站得住脚,就去见个律师吧。


--------------------------------------------------------------------------------

如何把我的 Perl程式码编译成 byte code或 C?
Malcolm Beattie已经写了一个多功能的後端编译器,可以从 CPAN取得,它就能做到这两项功能。1997 年二月是 alpha测试版的最後几个阶段,这代表着若你是个程式设计 员而非寻找万灵解药的人,那麽参与其测试就会充满趣味。

请了解光是编译成 C 其本身或在本质上并不能保证它就会跑得快更多。那是因为除了 在运气好的状况中有一堆可以衍生成出来的原生形态外,平时的 Perl 执行系统环境依然 存在因此依然会花差不多长的执行时间与占用差不多大小的记忆空间。大多数程式能省下 来的不过是编译时间,这使执行速度顶多快 10-30%。有些罕见的程式能真正从中受利 (例如增快好几倍),但这还得配合原始码的微调。

Malcolm 将会主导 Perl 5.005 版的发展并试着将其编译器与多执行绪部份的工作融合进主要的发行版本里。

你或许会惊讶地发现,现行版本的编译器做出来的执行档大小跟你的 Perl直译器一样大,有时更大些。那是因为依照现在的写法,所有的程式皆转成一个被 eval()的大叙述。只要建造一个动态连结的 libperl.so程式库,并将之连结起来,你就可以戏剧性地减少这 种浪费。参看 perl原始码套件中的 INSTALL pod档案以获得更详尽的讯息。如果你用这方法连结你主要的 perl执行档,就能使它变得很渺小。举例来说,在作者之一的系 统里, /usr/bin/perl只有 11k“小”而已!


--------------------------------------------------------------------------------

如何才能让 '#!perl'在 [MS-DOS,NT,...]下作用?
OS/2下只要用:

extproc perl -S -your_switches

当作 *.cmd档案的第一行 (-S 是因 cmd.exe中其 `extproc'处理的臭虫才要的)。DOS使用者应先制作一个相对的 batch 档案然後将它以 ALTERNATIVE_SHEBANG 的方式写成程式。(更多讯息在原始码版本的 INSTALL档案里)

若安装 Activeware版的 Win95/NT 专用 Perl,它会更动 Registry的内容,把 .pl 的扩充档名与 perl直译器结合。如果你安装另一版本或是用 WinGCC建构你自己的 Win95/NT用 Perl,那你就得自己更动 Registry的内容了。

麦金塔的 perl程式将会有适当的创造者与形态 (Creator and Type),所以双击它们就会执行这些 perl 应用程式。

重要:不论你做什麽,请千万不要因为觉得沮丧,就把 perl 直译器丢到你的 cgi-bin目录下,好让你的 web 伺服器能执行你的程式。这是一个非常大的安全漏洞。花点时间想 想怎样才是正确的做法吧。


--------------------------------------------------------------------------------

我能利用命令列写出有用的程式吗?
可以。详情请看 perlrun。以下有些范例 (假设用的是标准的 Unix shell引言规则)。

#把第一栏和最後一栏相加
perl -lane 'print $F[0] + $F[-1]'

#辨别是否为文字档
perl -le 'for(@ARGV) {print if -f && -T _}' *

#移除 C程式中的说明
perl -0777 -pe 's{//*gs' foo.c

#让档案年轻一个月,躲避追杀的魔鬼 (daemon)
perl -e '$X=24*60*60; utime(time(),time() + 30 * $X,@ARGV)' *

#找出第一个未用的 uid
perl -le '$i++ while getpwuid($i); print $i'

#显示合理的使用说明路径 (manpath)
echo $PATH | perl -nl -072 -e '
s![^/+]*$!man!&&-d&&!$s{$_}++&&push@m,$_;END{print"@m"}'

好吧,最後一个例子事实上是「perl程式困惑化」竞赛 (Obfuscated Perl)的 参赛作品。


--------------------------------------------------------------------------------

为何一行的 perl程式无法在我的 DOS/Mac/VMS系统上运作?
问题通常出在那些系统的命令解译器对於参数的引用与 Unix shells 所作的解释不同,而後者很不幸的是这些一行 perl 的生父。在某些系统,也许你得把单引号改成双引号,但这却是你万万 不可在 Unix或 Plan9系统上作的事。你也许还得把一个 %改成 %%。

例如说:

# Unix
perl -e 'print "Hello world/n"'

# DOS,等。
perl -e "print /"Hello world/n/""

# Mac
print "Hello world/n"
(然後执行 "Myscript"或按 Shift-Command-R)

# VMS
perl -e "print ""Hello world/n"""

问题是,这些方法没有一个是完全可靠的:它都得看命令解译器的脸色。在 Unix中,前两者通常可以用。在 DOS下,两者可能都没有用。若 4DOS是命令解译器,下面此法可能比 较有希望:

perl -e "print "Hello world/n""

在 Mac 下,端视你所用的环境为何。 MacPerl所附的 shell,或是 MPW, 其所支援的参数格式有不少都蛮像 Unix shells的,除了它自在地使用 Mac 的非 ASCII字元当成控制字元。

恐怕我得说这问题并没有一般解。白话一点说,它真是一团乱。

[部份答案是由 Kenneth Albanowski 所提供的。]


--------------------------------------------------------------------------------

我得去哪里学 Perl的 CGI或是 Web程式设计呢?
就模组来说,去 CPAN抓 CGI 和 LWP 两个模组。就书本来看,参考关於书那部份里特别和 web 相关的问题。若有与 web相关的疑难杂症,像“为何我收到 500错误”或“它在命令列模式下跑得好好的,怎麽不能在浏览器下正常执行”时,请参看:

The Idiot's Guide to Solving Perl/CGI Problems, by Tom Christiansen
http://www.perl.com/perl/faq/idiots-guide.html

Frequently Asked Questions about CGI Programming, by Nick Kew
ftp://rtfm.mit.edu/pub/usenet/news.answers/www/cgi-faq
http://www3.pair.com/webthing/docs/cgi/faqs/cgifaq.shtml

Perl/CGI programming FAQ, by Shishir Gundavaram and Tom Christiansen
http://www.perl.com/perl/faq/perl-cgi-faq.html

The WWW Security FAQ, by Lincoln Stein
http://www-genome.wi.mit.edu/WWW/faqs/www-security-faq.html

World Wide Web FAQ, by Thomas Boutell
http://www.boutell.com/faq/

(译者:上面第叁份文件,Perl-CGI-FAQ的中译版可在 http://2Ti.com/cgi-bin/2T/perl/perl-cgi-faq-chi/ 处取得。最後一份(WWW FAQ)的中译版可自 http://www.acer.net/document/cwwwfaq/ 取得。)


--------------------------------------------------------------------------------

在哪可以学到用 Perl作物件导向程式设计?
perltoot是个好开始,然後你可以再参考 perlobj 和 perlbot。Perltoot直到 5.004版本才诞生,但你可以从 http://www.perl.com/CPAN/doc/FMTEYEWTK/下取得 (pod、html,或 postscript 格式)。


--------------------------------------------------------------------------------

哪里可以学到将 C与 Perl相连结? [h2xs, xsubpp]
若你要从 Perl程式呼叫 C,就自 perlxstut开始向 perlxs ,xsubpp ,及 perlguts前进。反之,则读 perlembed ,perlcall ,及 perlguts 。别忘了 你可以从各模组的作者如何写他们的模组及解决他们的问题中学到很多。


--------------------------------------------------------------------------------

我已经读了 perlembed, perlguts,等等,但我仍然无法将 perl嵌入我的 C程式,我做错了什麽?
自 CPAN 下载 ExtUtils::Embed 套件,然後执行 `make test'。如果测试成功,就一遍又一遍地读那些 pod 说明档案。若它失败了,参看 perlbug并送一份内有 make test TEST_VERBOSE=1 与 perl -V输出的报告。


--------------------------------------------------------------------------------

当我试着执行我的程式时,我收到某项讯息。它代表什麽意思?
perldiag有一份完整的 perl错误与警告讯息列表,并附有说明文字。你也可以用 splain程式 (伴随 perl而来)去解释这些错误讯息:

perl program 2>;diag.out
splain [-v] [-p] diag.out

更改你的程式让它替你解释这些讯息也可以:

use diagnostics;



use diagnostics -verbose;


--------------------------------------------------------------------------------

什麽是 MakeMaker?
此模组 (亦为标准 perl 套件之一部份)设计的目的是要替一个模组从一 Makefile.PL 中自动撰写出一个 Makefile。详情请看 MakeMaker 。
 
我该如何使用正规表示式才不至於写出不合语法且难以维护的程式码?
以下提供叁个技巧使得你的正规表示式易懂又好维护。

在正规表示式外围作注解。
用 Perl的注解方式描述你所作的事以及你如何作到它。
#把每行变成「第一个字、冒号,和剩馀的字元数」这样的格式。
s/^(/w+)(.*)/ lc($1) . ":" . length($2) /ge;

在正规表示式内部作注解。
/x修饰子会要直译器忽略正规表示式内的任意空白 (在特定字元类别 [character class]中例外),同时也让你在式子中使用平常的注解方法。你应该能想像得到, 加上一些空白与注解帮助会有多大。
/x让你把下面这行:

s{'"]*|"gs;

变成:

s{ '"] * #有零个以上、不是 >;、 ',或 "的字元
| #或者是
".*?" #一段双引号圈起来的区域 (吝啬式对应)
| #或者是
'.*?' #一段单引号圈起来的区域 (吝啬式对应)
) + #以上区域出现一次或多次
>; #箭头括弧区结束
}{}gsx; #用空字串来替换;也就是杀掉

虽然它看来还是不够简明易懂,但至少大大有助於解释这个模式 (pattern)的意义。

换个不同的区隔字元 (delimiter)。
尽管我们平常都把正规表示式的模式 (patterns)想作是以 /字元来区隔,但实际上用几乎任何字元来作都行。perlre文件中有说明。例如,上面的 s///便是用大括号来当区隔字元的。选择另一个区隔字元可以免除在模式中得避开 (quote)区隔字元的困扰。例如:
s///usr//local///usr//share/g; #选错区隔字元的後果【译注:
#常被戏称为「搭牙签」症候群

s#/usr/local#/usr/share#g; #这样不是好多了?!


--------------------------------------------------------------------------------

我无法对应到超过一行的内容,哪里出了问题?
若不是你的字串里少了换行字元,就是你在模式里用了错误的修饰子。

有很多方法将多行的资料结合成一个字串。如果你希望在读入输入资料时自动得到 这项功能,你得重新设定 $/变数 (若为段落,设成 '';若要将整个档案读进一字 串,设成 undef ),以容许你一次能读入一行以上的输入。

请参考 prelre,其中有选择 /s或 /m (或二者都用)的说明: /s让万用字元 (``.'')能对应到换行字元【译注:通常换行字元不在 ``.'' 的对应范围内】, /m则让 ``^''和 ``$''两个符号能够对应到任何换行字元的前後,而不只是像平常 那样只能对应到字串头尾。你所需要确定的是你的确有个多行的字串。

例如说,以下这个程式会侦测出同一段落里重覆的字,即使它们之间有换行符号相隔 (但是不能隔段)。在这个例子里,我们不需要用到 /s,因为我们并未在任何要跨行对应的正规表示式中使用 ``.''。我们亦无需使用 /m,因为我们不想让 ``^''或 ``$''去对应 到字串中每个换行字元前後的位置。但重点是,我们得把 $/ 设成与内定值相异的值,否则我们实际上是无法读入一个多行的资料的。

$/ = ''; #读入一整段,而非仅是一行。
while ( ) {
while ( //b(/w/S+)(/s+/1)+/b/gi ) {
print "在段落 $.找到重复的字 $1/n";
}
}

以下的程式能找出开头为 ``From ''的句子 (许多邮件处理程式都会用到这个功能):

$/ = ''; #读入一整段,而非仅是一行。
while ( ) {
while ( /^From /gm ) { # /m使得 ^也会对应到 /n之後
print "开头为 From的段落 $./n";
}
}

以下的程式会抓出在一个段落里所有夹在 START与 END之间的东西。

undef $/; #把整个档案读进来,而非只是一行或一段
while ( ) {
while ( /START(
}


--------------------------------------------------------------------------------

我如何取出位於不同行的两个模式间之内容?
你可以使用看起来有点怪的 Perl ..运算元 (在 perlop文件中有说明):

perl -ne 'print if /START/ .. /END/' file1 file2 ...

如果你要的是整段文字而非各单行,你该使用:

perl -0777 -pe 'print "$1/n" while /START(.*?)END/gs' file1 file2 ...

但是当 START和 END之间的东西作巢状(内含)式分布 (nested occurrences)的时候 ,你便得面对本篇中所提到的对称式文字对应的问题。


--------------------------------------------------------------------------------

我把一个正规表示式放入 $/但却没有用。错在哪里?
$/必须是个字串,不能是一个正规表示式。Perl得留一手,让 Awk还有点可骄傲 之处。

事实上,如果你不介意把整个档案读入记忆体的话,不妨试试看这个:

undef $/;
@records = split /your_pattern/, ;

Net::Telnet模组 (CPAN里有)具有一项功能,可监视着输入流 (input stream)、等待特定的模式出现,或是在规定时间到了还没等到时,送出逾时 (timeout)讯息。

##开一个有叁行的档案
open FH, ">;file";
print FH "The first line/nThe second line/nThe third line/n";
close FH;

##取得一个可读/写的档案处理把手
$fh = new FileHandle "+ $fh);

##等到第二行出现了,就把第叁行印出来。
$file->;waitfor('/second line/n/');
print $file->;getline;


--------------------------------------------------------------------------------

如何在 LHS端【译注:式子中运算元左端部份】作不区别大小写式的替换,但在 RHS端【右端】保留大小写区别?
答案端看你如何定义「保留大小写区别」(preserving case)。下面这个程式依照每个 字母的顺序,在替换动作完成後保留原来的大小写。如果用来替换的字其字母数比被替 换者多,那麽最後一个字母的大小写就会被用作决定替换字剩馀字母的大小写之依据。

#原作者为 Nathan Torkington,经 Jeffrey Friedl调整
#
sub preserve_case($$)
{
my ($old, $new) = @_;
my ($state) = 0; # 0 = no change; 1 = lc; 2 = uc
my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));
my ($len) = $oldlen $oldlen) {
if ($state == 1) {
substr($new, $oldlen) = lc(substr($new, $oldlen));
} elsif ($state == 2) {
substr($new, $oldlen) = uc(substr($new, $oldlen));
}
}
return $new;
}

$a = "this is a TEsT case";
$a =~ s/(test)/preserve_case($1, "success" /gie;
print "$a/n";

这会印出:

this is a SUcCESS case


--------------------------------------------------------------------------------

如何使 /w对应到附重音记号 (accented)的字元?
请参考 perllocale说明文件。


--------------------------------------------------------------------------------

如何作一个适合不同 locale【译注:国家、地区在文字编码上各自的惯例】的 /[a-zA-Z]/对应?
一个字母可以用 /[^/W/d_]/表示,不论你的 locale为何。非字母则可用 /[/W/d_]/表示 (假定你不把 ``_''当成字元)。


--------------------------------------------------------------------------------

在一个正规表示式里如何引入 (quote)变数?
Perl解析器於间隔字元不是单引号时,会展开正规表示式里的 $variable及 @variable变数。同时也要记得,一个 s///替换式右侧部份是当成双引号括起来处理的 (详情请参看 perlop说明文件)。更别忘记,任何一个正规表示式里的特殊字元都会先被解译、处理, 除非你在替换模式前加 /Q。以下即为一例。

$string = "to die?";
$lhs = "die?";
$rhs = "sleep no more";

$string =~ s//Q$lhs/$rhs/;
# $string现在成了 "to sleep no more"

少了 /Q,则这个正规表示式同时也会错误地对应到 ``di''。【译注:因为 /die?/ 这个式子表示 ``di''後头的 ``e''可有零个或一个】


--------------------------------------------------------------------------------

/o到底是干麽用的?
当你在一个正规表示式里用一个变数来作对应时,每次通过它时都要重新评估一次(re- evaluation),有时甚至要重新编译(recompilation)。/o会在第一次用到那个变数 时把它锁定。在一个无变数的正规表示式里面,此情形永远为真,而且事实上,当你整 个程式在被编译成内部(位元)码的同时,你所用的模式亦然。

除非在模式里有变数转译的情况发生,否则使用 /o是无关痛痒的。在模式中有变数并且又有 /o修饰子的情况下,正规表示式引擎则既不会知道也不会去管这个模式在 第一次评估之後其中变数是否又有所改变。

/o常被用来额外提高执行效率。当重覆评估无关紧要 (因为事先知道该变数的 值不会改变);或是在有些罕见的情况下,故意不让正规表示式引擎察觉到变数值已改变 时,便可透过此一手段,避免持续评估,来达到提高效率的目的。

下面以一个 ``paragrep'' (「段落grep」)程式作范例:

$/ = ''; #使用段落模式
$pat = shift;
while () {
print if /$pat/o;
}


--------------------------------------------------------------------------------

如何使用正规表示式将档案中 C语言样式的注解删掉?
虽然这件事实际上做得到,但却比你想像中更加困难。例如下面的单行小程式 (one-liner):

perl -0777 -pe 's{//*gs' foo.c

只能在大部分(但非全部)的情况下成功。你知道,这程式对某些种类的 C程式显得太 简陋、单纯了,尤其是那些被双引号括起来、看似注解的字串。针对它们,你需要像 这个 Jeffrey Friedl所写的这样的程式:

$/ = undef;
$_ = ;
s#//*[^*]*/*+([^/*][^*]*/*+)*/|("(//.|[^"//])*"|'(//.|[^'//])*'|/n+|.[^/"'//]*)#$2#g;
print;

当然,这程式可以用 /x加上空白与注解使它更容易让人看懂。


--------------------------------------------------------------------------------

我能用 Perl的正规表示式去对应成对的符号吗?
虽然 Perl的正规表示式比「数学的」正规表示式要来得强大,因为它们有追溯前段 (/1之类)这样方便的功能,但它们仍然不够强大。你依然得用非正规表示式 的技术去解析这类文字,譬如像两端用小括号或大括号包含起来的文字。

你可以在 http://www,或 (和 )。

CPAN中的 C::Scan模组包含一个这样的副常式供内部使用,但无说明文件。


--------------------------------------------------------------------------------

有人说正规表示式很贪婪,那是什麽意思?该如何避免它所带来的问题?
大部分的人所说的贪婪是指正规表示式会尽可能地对应到最多的东西。技术上来说,真正贪婪 的是量化子 (?, *, +,{})而非整个模式;Perl较喜欢作区域性的贪婪以得 到立即的快感,而不是对整个式子的贪婪。如欲使用同样的量化子作非贪婪式对应的话 【译注:即所谓的吝啬(stingy)式对应】,用 (??, *?, +?, {}?)。例如:

$s1 = $s2 = "I am very very cold";
$s1 =~ s/ve.*y //; #贪婪式;结果为 I am cold
$s2 =~ s/ve.*?y //; #吝啬式;结果为 I am very cold

注意到在第二个替换中一碰到 ``y''就停止整个对应了吗? *?量化子有效率地告诉正 规表示式引擎,一但对应到一个模式,就马上把控制权移交下去,这行为就好比你手上有 个烫手山芋时所会采取的行动一样。


--------------------------------------------------------------------------------

如何处理每一行的每个字?
用 split函数:

while () {
foreach $word ( split ) {
#在此作你想对 $word作的动作
}
}

请注意这里所谓的字和英文中对字的定义不同;它们可能只是一段连续的、非空白的 字元罢了。

若欲处理的是一连串纯字母的话,可以考虑用:

while () {
foreach $word (m/(/w+)/g) {
#在此作你想对 $word作的动作
}
}


--------------------------------------------------------------------------------

我如何印出文字出现频率或行出现频率的纲要?
要作到这点,你得解读、分析输入字元流内的每个字。在此我们假设所谓的「字」 局限於一串由字母、连字号,或撇号所组成的字,而非前一问题中提到的一串 非空白字元集合那种概念:

while () {
while ( /(/b[^/W_/d][/w'-]+/b)/g ) { # "`sheep'"会漏失掉
$seen{$1}++;
}
}
while ( ($word, $count) = each %seen ) {
print "$count $word/n";
}

如果你要算行数,则用不着使用正规表示式:

while () {
$seen{$_}++;
}
while ( ($line, $count) = each %seen ) {
print "$count $line";
}

如果你希望这些输出经过排列,请参看有关 Hashes的那部分。


--------------------------------------------------------------------------------

如何能作近似对应?
参考 CPAN里的 String::Approx模组。


--------------------------------------------------------------------------------

我如何有效率地一次对应多个正规表示式?
下面是个超没效率的例子:

while () {
foreach $pat (@patterns) {
if ( /$pat/ ) {
# do something
}
}
}

要避免以上的方法,要不你就选用 CPAN 中几个实验性的正规表示式扩充模组其中一个 (对你的目的来说可能效率还是不够好),或是自己写个像下面这样的东西 (自 Jeffrey Friedl书中的一个函式所得到的灵感):

sub _bm_build {
my $condition = shift;
my @regexp = @_; #这里不可用 local(),得用 my()
my $expr = join $condition =>; map { "m//$regexp[$_]/o" } (0..$#regexp);
my $match_func = eval "sub { $expr }";
die if $@; # $@【错误变数】里面有东西;这不该出现!
return $match_func;
}

sub bm_and { _bm_build('&&', @_) }
sub bm_or { _bm_build('||', @_) }

$f1 = bm_and qw{
xterm
(?i)window
};

$f2 = bm_or qw{
/b[Ff]ree/b
/bBSD/B
(?i)sys(tem)?/s*[V5]/b
};

# 我 /etc/termcap
while ( ) {
print "1: $_" if &$f1;
print "2: $_" if &$f2;
}


--------------------------------------------------------------------------------

为何我用 /b作字界搜寻时会失败呢?
有两个常见的错误观念是将 /b做为 /s+的同义词,还有把它当成界定空白及非空白字元间的边界。两者都不对。/b是介於一个 /w字元和 /W 字元之间的部分(亦即 /b是一个「字」的边缘)。它是一个长度为 0的标的物,就像 ^、$,以及所有其它的标示字元 (anchors)一样,在对应时并不消耗、占掉任何字元。perlre使用说明中对各正规表示式超字元 (metacharacters)的特性和使用都有做解释。

以下是错误使用 /b的例子,并附上修正:

"two words" =~ /(/w+)/b(/w+)/; #错误!
"two words" =~ /(/w+)/s+(/w+)/; #正确

" =matchless= text" =~ //b=(/w+)=/b/; #错误!
" =matchless= text" =~ /=(/w+)=/; #正确

虽然它们也许不能作到你以为它们能作的事,但 /b及 /B仍然相当有用。要看看正确使用 /b的范例,请参考「如何於多行文字中抓出重复字」一问题内所附之范例。

/Bis/B这个模式是使用 /B的一个例子。它只会对应到出现在一个字内部的 ``is'',例如 ``thistle'',但不会对应到 ``this''或 ``island''。


--------------------------------------------------------------------------------

为什麽每当我用 $&, $`,或 $'时程式的速度就慢下来了呢?
因为不管在程式中哪一个角落,一旦 Perl看见你需要这类的变数时,它就得 在每次模式对应时准备好提供这些变数的值。$1, $2 等等的使用也是以同样的方式处理的。所以每当你的模式中含有捕捉用的小括号 (capturing parentheses)时, 你就得付出同样的代价。但若你从不在你的程式中用到 $&等这些东西,那麽 没有捕捉用小括号的正规表示式就不用付出额外的速度作代价。所以,请尽可能避免使用 $&, $'及 $`,但若真的无法避免 (有些演算法的确需要它们),就尽量用糸 吧,反正你已经付出代价了。


--------------------------------------------------------------------------------

正规表示式中的 /G能给我什麽好处?
/G在一个对应式或替换式中要和 /g修饰子合起来用 (若无 /g它就会被忽眷 。它是用来标示上一个成功的模式对应完成後所停在的位置,亦即 pos()点。

例如说,你有一行信件文字是按标准的 mail及 Usenet记法 (就是以 >; 字元作开始)作成引言的,而你现在要把每个开头的 >;都换成 :。那麽你可以用下面的方法来作:

s/^(>;+)/':' x length($1)/gem;

或者使用 /G,更简单也更快:

s//G>;/:/g;

更复杂的方法可能要用到记号赋予器 (tokenizer)。下面看来像 lex语法分析器程式 码的例子是 Jeffrey Friedl提供的。它在 5.003 版因为其版本中的臭虫而无法执行,但在 5.004或以上的版本的确可行。(请注意到 /c的使用,它的存在是为了防止 /g在对应失败时将搜寻位置归零到字串的开始。)

while () {
chomp;
PARSER: {
m/ /G( /d+/b )/gcx && do { print "number: $1/n"; redo; };
m/ /G( /w+ )/gcx && do { print "word: $1/n"; redo; };
m/ /G( /s+ )/gcx && do { print "space: $1/n"; redo; };
m/ /G( [^/w/d]+ )/gcx && do { print "other: $1/n"; redo; };
}
}

当然,上面这个本来也可以写成像

while () {
chomp;
PARSER: {
if ( //G( /d+/b )/gcx {
print "number: $1/n";
redo PARSER;
}
if ( //G( /w+ )/gcx {
print "word: $1/n";
redo PARSER;
}
if ( //G( /s+ )/gcx {
print "space: $1/n";
redo PARSER;
}
if ( //G( [^/w/d]+ )/gcx {
print "other: $1/n";
redo PARSER;
}
}
}

但是这麽作就不能让那些正规表示式的式子上下对齐一目了然了。


--------------------------------------------------------------------------------

Perl正规表示引擎是 DFAs或 NFAs?它们是 POSIX相容的吗?
尽管 Perl的正规表示式看似 egrep(1)程式的 DFAs (deterministic finite automata,决定式有限自动机)特性,但事实上为了具备「退回原路」(backtracking) 与「追溯前段」( backreferencing)的功能,它们实作时是用 NFAs (non-deterministic finite automata,非决定式有限自动机)的。并且它们亦非 POSIX式的,因为那样会造成在所有情况下都有最差的表现。(似乎有些人较注重确 保一致性,即使那同时也确保了缓慢的速度)。你可以在 Jeffrey Friedl所着的 ``精通正规表示式'' (Mastering Regular Expressions)一书中 (O'Reilly出版) ,获得所有你想知道关於这些事的所有细节(在 perlfa里面有该书的详细资料)。


--------------------------------------------------------------------------------

在无递回的场合下用 grep或 map有什麽不对?
严格地说来,没有什麽不对。不过就格式的角度看来,这样会造成不易维护的程式码。 因为你是使用了他们的副作用 (side-effects)而非使用他们的传回值,不幸的是, 副作用容易让人搞混。无递回式的 grep()在写法上不如 for (嗯,技术上说是 foreach啦)回圈。


--------------------------------------------------------------------------------

如何对应多位元组字母所构成的字串?
这很难,并且还没有好的方法。Perl 并不直接支援多位元组字母。它假装一个位元组和 一个字母是同义的。下面提供的方法来自 Jeffrey Friedl,他有一篇登在 Perl期刊 (The Perl Journal)第五期的文章讨论的正是这个问题。

假设有一种怪异的火星语编码协定,其中每两个大写的 ASCII字母代表一个火星 字母 (譬如 ``CV''这两个位元组代表一个火星字母,就像 ``SG''、``VS''、``XX'',等双字元组一样)。至於其它位元则和在 ASCII 里一样表示单一字元。

所以,像 ``I am CVSGXX!''这样的火星字串用了 12个位元去表示九个字母 'I',' ' ,'a','m',' ','CV','SG','XX','!'。

现在假设你要搜索这个字母:/GX/。Perl并不懂火星语,所以它会找到 ``I am CVSGXX!''中 ``GX'' 这两个位元,即使事实上这个字母并不在其中:它之所以看来像是在那儿是因为 ``SG''和 ``XX''紧临在一起罢了,实际上并非真有 ``GX''存在。这是个大问题。

以下有些处理的方法,虽然都得付出不少代价:

$martian =~ s/([A-Z][A-Z])/ $1 /g; #让相邻的「火星」位元不再相邻
print "找到 GX了!/n" if $martian =~ /GX/;

或像这样:

@chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
#上面那行在理念上近似於: @chars = $text =~ m/(.)/g;
#
foreach $char (@chars) {
print "找到 GX了!/n", last if $char eq 'GX';
}

这样也可以:

while ($martian =~ m//G([A-Z][A-Z]|

不然乾脆这样:

die "对不起,Perl尚未支援火星文 )-:/n";

除此之外,CPAN里面有个范例程式能将半宽 (half-width)的片假名转成全宽 (full-width) [以 Shift-JIS或 EUC编码的],这是拜 Tom之赐才有的成果。

现在已有很多双 (和多)位元编码法被广泛的采用。这些方法中有些是采用 1-,2-, 3-,及 4位元组字母,混合使用。

我能拿到 Perl的 BNF/yacc/RE吗?
不行,引用 Chaim Frenkel的话:“Perl的语法无法被简化到可以用 BNF 表示。解析Perl的工作是分散於 yacc、lexer、烟雾和镜子之间。”


--------------------------------------------------------------------------------

$@%*这些符号是什麽意思?我怎麽知道何时该使用他们呢?
他们都是指定形态 (type)用的符号,如同 perldata里所详述的:

$纯量值 (scalar) (数字,字串或参考值 [reference])
@阵列
%杂凑阵列 (关连阵列)
*代表同一个变数名的所有类形。在第四版中它们常用来达到指标
(pointers)的功能,但现在在新版的 perl中这个角色已被参
考值 (reference)取代了。

虽然这些符号在某些场合下可省略,但建议你随处都用。

有些其他的符号你可能会碰到但却不是指定形态用的有:

这是用来从一个档案把手 (filehandle)里输入一份记录
/取某样东西的参考值 (reference)

注意 不是用来指定档案的形态,亦非此把手的名字。它只是 将这个运算子用在 FILE这个把手上。在纯量的情境 (scalar context) 下,它自 FILE 把手一次读入一行 (嗯,该说一笔记录,参看 $/),在序列情境 (list context)下,则一次将 全部的内容读 入。当对档案使用开、关或其它 之外的动作、或甚至只是提到把 手时,切记不要使用 。下面的用法是正确的:eof(FH) , seek(FH, 0,2) 以及 ``copying from STDIN to FILE''。


--------------------------------------------------------------------------------

字串加引号或使用分号及逗号是否绝对必要/还是完全没必要?
通常一个没有冠上形态符号的字 (bareword)是不需被纳入引号里的,但在大多数 的情况下或许该这麽做 (在use strict下则是必须的)。但由一个简单的字(不 能是一个已定义的副函数之名称)所构成的索引值,和 =>;左端的运算子,都会被视为已纳入引号了:

这些是和这些一样的
------------ ---------------
$foo{line} $foo{"line"}
bar =>; stuff "bar" =>; stuff

一个区块末端的分号可有可无,一个序列的最後一个逗号亦同。良好的写作风格 (参看perlstyle)中建议除了在单行程式 (one-liners)的情况外都将他们加上去:

if ($whoops) { exit 1 }
@nums = (1, 2, 3);

if ($whoops) {
exit 1;
}
@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);


--------------------------------------------------------------------------------

我如何暂时滤掉警告讯息?
$^W变数 (在 perlvar中有说明)控制一个区块在执行期 (runtime)的警告讯息:

{
local $^W = 0; #暂时关掉警告讯息
$a = $b + $c; #我知道这些变数可能未定义
}

注意,像所有的标点符号变数一样,目前不能对 $^W用 my,只能用 local()。

一个发展中的新 use warnings编译器指挥模组 (pragma) 提供了更精细的控制。好奇宝宝们应该翻翻 perl5-porters 邮件论坛的档案库以获得更详细的说明。


--------------------------------------------------------------------------------

什麽是一个扩充 (extension)?
一种从 Perl呼叫编译好的 C程式码的方法。阅读 perlxstut是个多了解扩充(extensions)的好方法。


--------------------------------------------------------------------------------

为何 Perl运算子的优先顺序和 C的不一样?
事实上它们是相同的。所有 Perl自 C借过来的运算子都具备与原来在 C 中相同的优先顺序。问题出在那些 C没有的运算子,特别是那些将其右方一律当成序列情境对待的函数,例如 print, chmod, exec等等。这类的函数被称作“序列运算子”(list operators),在 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变成负四而非正四。他同时也会“向右靠”(right-associate),意思是说 2**3**2 代表二的九次方,而不是八的平方。


--------------------------------------------------------------------------------

我如何宣告/生成一个资料结构 (structure)?
一般来说,我们不 ``宣告''一个结构。用一个 (通常是匿名的) 杂凑阵列的参考值 (hash reference)即可。参看 perlref 以及 perldsc,里面有更多资料。以下是一个范例:

$person = {}; #新的不具名杂凑阵列
$person->;{AGE} = 24; #把 AGE栏的值设成 24
$person->;{NAME} = "Nat"; #将 NAME栏设成 "Nat"

如果你要的是更严谨的写法,看看 perltoot 。


--------------------------------------------------------------------------------

我如何创造出一个模组 (module)?
一个模组就是一个放在同名档案里的包裹(package)。例如,Hello::There模组会 放在Hello/There.pm。perlmod 里有详尽说明。Exporter 也会很有帮助。如果你正在写一个 C 或是混合了 C及 Perl 的模组,那麽你就该读 perlxstut 。

下面是个方便的样板,你也许希望在撰写第一个模组时将他派上用场。记得要改名 字。

package Some::Module; #假设是 Some/Module.pm

use strict;

BEGIN {
use Exporter ();
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);

##设定版本以备版本检查之用;去掉 "#"号即可使用。
## $VERSION = 1.00;

#如果有使用 RCS/CVS,那应该考虑将下一行保留,
#但是小心两位数版本编号可能造成的影响。
$VERSION = do{my@r=q$Revision: 1.1 $=~//d+/g;sprintf '%d.'.'%02d'x$#r,@r};

@ISA = qw(Exporter);
@EXPORT = qw(&func1 &func2 &func3);
%EXPORT_TAGS = ( ); #例如: TAG =>; [ qw!name1 name2! ],

#整个包裹要输出的全域变数(exported package globals)在此,
#还有其他选择要输出的函数。
@EXPORT_OK = qw($Var1 %Hashit);
}
use vars @EXPORT_OK;

#没有输出的全域变数在此。
use vars qw( @more $stuff );

#起始包裹内的全域变数,首先是要输出的那几个。
$Var1 = '';
%Hashit = ();

#接下来是其他的 (还是能以 $Some::Module::stuff的方式撷取他们的值)
$stuff = '';
@more = ();

#所有以档案为范围的变数名都
#必须在让後面的函数使用前先创造出来。

#以档案为范围的变数名在此。
my $priv_var = '';
my %secret_hash = ();

#下面是一个以档案为限的函数,当作一个闭包
#可以用 &$priv_func的方式呼叫它;但不能使用原型定义
my $priv_func = sub {
#程式码放在这里
};

#不论是否要输出,都记得要将你的函数造出来。
#别忘了在 {}括号间放些有趣的内容。

sub func1 {} #没有定义原型
sub func2() {} #定原型为 void
sub func3($$) {} #定原型为两个纯量值

#这个函数虽然未被输出,但是可以被呼叫
sub func4(/%) {} #定原型为一个杂凑阵列的参考值

END { } #模组清洁大队在此 (global destructor)

1; #模组必须传回真值


--------------------------------------------------------------------------------

我如何创造一个类别 (class)?
perltoot 里面有对於类别和物件的介绍, perlobj 和 perlbot 也有。


--------------------------------------------------------------------------------

我如何辨别一个变数是否被污染了(tainted)?
参考 Laundering and Detecting Tainted Data。以下是个范例 (里面没有用到任何系统呼叫,因为 kill() 没有将任何程序交给讯号):

sub is_tainted {
return ! eval { join('',@_), kill 0; 1; };
}

然而,此方法会触发 -w参数的警告讯息。目前并无任何不会触发 -w的方法可下侦测变数污染 -- 就把警告讯息当成是在提醒你,该把所有可能被污染的资料给 ``漂白'' (untaint)。

【译注:这里所提的 ``被污染'' (tainted),指的是当使用 -T这个参数时,或当 perl程式做 setuid或 setgid模式执行时 (在 UNIX 下), perl会自动将有安 全顾虑的变数列为受污染, 也就是 tainted。除非先做解除污染 (untaint)处理,否则 perl不会容许受污染的变数做出可能危害系统的举动。因此 CGI程式应尽可能地使用 -T这个参数以策安全。】


--------------------------------------------------------------------------------

闭包 (closure)是啥?
关於闭包的说明,请看 perlref 。

闭包 (closure)是个精确但又很难解释的电脑名词。在 Perl 里面,闭包是以 匿名函数的形式来实现,具有持续参照位於该函数范围之外的文字式变数值的能力。 这些外部的文字变数会神奇地保留它们在闭包函数最初定义时的值 (深连结)。

如果一个程式语言容许函数递回另一个函数的话 (像 Perl 就是),闭包便具有意 义。要注意的是,有些语言虽提供匿名函数的功能,但却无法正确处理闭包; Python 这个语言便是一例。如果要想多了解闭包的话,建议你去找本功能性程式 设计的教科书来看。Scheme这个语言不仅支援闭包,更鼓励多加使用。

以下是个典型的产生函数的函数:

sub add_function_generator {
return sub { shift + shift };
}

$add_sub = add_function_generator();
$sum = &$add_sub(4,5); # $sum现在是 9了

闭包用起来就像是个 函数样板,其中保留了一些可以在稍後再填入的空格。 add_function_generator() 所递回的匿名函数在技术上来讲并不能算是一个闭包, 因为它没有用到任何位在这个函数范围之外的文字变数。

把上面这个例子和下面这个 make_adder()函数对照一下,下面这个函数所递回的匿名函数中使用了一个外部的文字变数。这种指名外部函数的作法需要由 Perl递回一个适当的闭包,因此那个文字变数在匿名函数产生之时的值便永久地被锁进闭 包里。

sub make_adder {
my $addpiece = shift;
return sub { shift + $addpiece };
}

$f1 = make_adder(20);
$f2 = make_adder(555);

这样一来 &$f1($n) 永远会是 20加上你传进去的值 $n ,而 &$f2($n) 将 永远会是 555加上你传进去的值 $n。$addpiece的值会在闭包中保留下来。

闭包在比较实际的场合中也常用得到,譬如当你想把一些程式码传入一个函数时:

my $line;
timeout( 30, sub { $line = } );

如果要执行的程式码当初是以字串的形式传入的话,即 '$line = ' ,那麽 timeout() 这个假想的函数在回到该函数被呼叫时所在的范围後便无法再撷取 $list这个文字变数的值了。


--------------------------------------------------------------------------------

何谓变数自杀而我又该如何防止它?
变数自杀指的是 (暂时或是永久)地失去一个变数的值。造成这个现象的原因是做范围界定的 my() 和 local()和闭包或 foreach()回圈变数及函数参数相互影响 所致。

从前【在旧版 perl的时代】大家写程式的时候很容易因为这样而不小心把变数值 给弄丢。但现在 perl提供了一些保护措施,因此犯这种错的机率要小多了。

my $f = "foo";
sub T {
while ($i++ 10, that =>; 20 } );

func( /&some_func );
func( sub { $_[0] ** $_[1] } );

传递档案把手
要创造出可以传递给函数使用的档案把手,你可以用 *FH或 /*FH (这叫 ``typeglobs'' --请参看 perldata ),或是使用旧名 FileHandle的 IO::File模组以动态方式来产生档案把手亦可,这两个模组都附在标准 Perl 版本内。
use Fcntl;
use IO::File;
my $fh = new IO::File $filename, O_WRONLY|O_APPEND;
or die "Can't append to $filename: $!";
func($fh);

传递正规表示式
想要将正规表现式传来传去,你需要的将是使用 CPAN 里一个实验性的正规表现式模组( Nick Ing-Simmons的 Regexp或 Ilya Zakharevich的Devel::Regexp),来传递字串,并且使用一个能捕捉例外情况的 eval叙述,或者你自己可以发明其他非常非常聪明的方式来做。以下就是一个如何以字串当作正规表现式,传入一个做比较的函数的例子:
sub compare($$) {
my ($val1, $regexp) = @_;
my $retval = eval { $val =~ /$regexp/ };
die if $@;
return $retval;
}

$match = compare("old McDonald", q/d.*D/);

确定绝对不要用以下的写法:

return eval "/$val =~ /$regexp/"; #错误

不然某人可以靠双引号括起来的字串以及 eval 双重解译的本质而偷偷插入 shell指令来作坏事。例如:

$pattern_of_evil = 'danger $ { system("rm -rf * &" } danger';

eval "/$string =~ /$pattern_of_evil/";

想学非常非常聪明的方法的读者可以参考 O'Reilly 出的 Mastering Regular Expressions这本书,作者是 Jeffrey Friedl。其中第 273页的 Build_MatchMany_Function()特别的有趣。在 perlfa中可以找到有关本书 的资料。

如何传递方法 (methods)
你可以用下面的方法传递一个物件方法给一个函式:
call_a_lot(10, $some_obj, "methname"
sub call_a_lot {
my ($count, $widget, $trick) = @_;
for (my $i = 0; $i $trick();
}
}

不然你就用个闭包 (closure) 把物件和它的方法以及其参数都包在一起:

my $whatnot = sub { $some_obj->;obfuscate(@args) };
func($whatnot);
sub func {
my $code = shift;
&$code();
}

你也可以研究 UNIVERSAL 类别中的 can()方法 (附於标准 Perl 版本中)。


--------------------------------------------------------------------------------

我如何生成一个静态变数?
就像与 Perl相关的其他事情一样,``条条大路通罗马'' (TMTOWTDI)。对其他语言来说叫做 ``静态变数'' (static variable)的东西,在 Perl里面可能是一个函数私有的变数(只有该函数自己看得到,且在不同的呼叫间保持定值),或是一个档案私有(file-private)变数(只有同一个档案中的函数才看得到)。

以下就是实作函数私有变数的程式:

BEGIN {
my $counter = 42;
sub prev_counter { return --$counter }
sub next_counter { return $counter++ }
}

prev_counter() 和 next_counter() 将会共用一个於编译时起始的私有变数 $counter。

要宣告一个档案私有(file-private)变数,你仍然得使用 my(),将它放在档案开头处最外围。假设现在是在 Pax.pm 这个档案里:

package Pax;
my $started = scalar(localtime(time()));

sub begun { return $started }

当用 use Pax或 require Pax载入此模组时,这个变数就会被起始。不过它不会被资源回收,像其他出了有效范围的变数那样,因为 begun()函数要用到它,但是没有其他函数能撷取它。这个变数不能以 $Pax::started 的形式来撷取,因为它所存在的范围与此包裹无关。它存在的范围是这个档案。可想见地,一个档案里可以放好几个包裹,而所有的包裹都撷取同一个私有变数,但从另一个档案中,即使是属於同一个包裹(package),也不能取得它的值。


--------------------------------------------------------------------------------

动态与文字式(静态)范围界定 (scoping)有何不同? Local()和 my()呢?
local($x) 将全域变数 $x的原值存起来,并在此函数执行期间赋予一个新 值,此值可以从此函数所呼叫的其他函数里看见。这整个步骤是在执行期间完成的,所以才叫做动态范围选取 (dynamic scoping)。local()影响的是全域变数,或者称作包裹变数或动态变数。

my($x)会创造一个只能在目前这个函数里看得见的新变数。这个步骤是在编译 期完成(compile-time),所以称作文字式或是静态范围选取。my()总是作用在私 有变数,也称作文字式变数或(不当地)称作静态(范围选取)变数。

例如:

sub visible {
print "var has value $var/n";
}

sub dynamic {
local $var = 'local'; #授予 $var这个全域变数
visible(); #一个暂时的新值
}

sub lexical {
my $var = 'private'; #新的私有变数,$var
visible(); # (无法从此函数外看到)
}

$var = 'global';

visible(); #会印出 global
dynamic(); #会印出 local
lexical(); #会印出 global

你可以发现在整个过程中 ``private''这个值都印不出来。那是因为 $var的值只存在於lexical() 函数的区块里面,对它所呼叫的函数来说是看不到的。

总结来说,local()不会产生你想像中的私有、区域变数。它只是将一个暂时的值 授予一个全域变数。如果你要的是私有的变数,那麽 my() 才是你要找的。

参看 perlsub ,里面有更详尽的解说。


--------------------------------------------------------------------------------

当同一个范围中有一个文字式变数时,我该如何去撷取同名的动态变数?
你可以透过符号式参考值 (symbolic references),把 use strict "refs"设定取掉。然後使用 $ {'var'} ,而非 $var。

local $var = "global";
my $var = "lexical";

print "lexical is $var/n";

no strict 'refs';
print "global is $ {'var'}/n";

如果你知道你所在的是哪一个包裹(package)的话,你可以直接指名,就像写 $Some_Pack::var这样。注意 $::var这个写法 并非表示目前此包裹 (package) 内的动态变数 $var,而是指在 main包裹(package) 里的那个,就等价於 $main::var。直接指定包裹(package)的名称虽然需要你把名字敲进程式码 中,但是它执行起来比较快,也避免和 use strict "refs" 起冲突。


--------------------------------------------------------------------------------

所谓深连结与浅连结 (deep and shallow binding)间有何不同呢?
在深连结中,匿名函数中所用到的文字式变数值是以该函数产生时所在的范围为准。在浅连结中,这些变数值是以函数被呼叫时所在的范围为准,如果在这个范围中恰巧有同名的变数,便使用这些当地变数的值。Perl总是使用文字式变数(就是以 my()创造的)式的深连结。然而,动态变数(也称作全域(global),区域(local),或包裹(package)变数)在功效上是浅连结。就把这当作是少用它们的另一个理由好 了。请参考 闭包 (closure)是啥? 一节。


--------------------------------------------------------------------------------

为何 "local($foo) = ;"无法正确地作用?
local()会把 =号右边以序列情境来对待。而 这个阅读的 动作,就像 Perl里许多的函数以及运算子一样,会自动分辨出自己被呼叫时所在的情境并且采取适当的作法。一般来说,scalar()函数可以帮点忙。这个函数实际上对资料本身不会有任何作用(与一般所认为的相反),但是会告诉它所作用的函数要以对待纯量值的方法来运算。如果那个函数没有预先定义好碰到纯量情境的行为,那麽它当然也帮不了你(例如 sort() 函数)。

然而,在以上这个例子 (local...)中,只要省略括号便可强制使用纯量情境:

local($foo) = ; #错误
local($foo) = scalar(); #可以
local $foo = ; #正确

其实在这个例子中,或许你该改用文字式变数 (lexical variables),不过会碰到 的问题跟上面一样:

my($foo) = ; #错误
my $foo = ; #正确


--------------------------------------------------------------------------------

我如何重新定义一个内建函数、运算子或是方法?
为什麽要这麽做?

如果你要覆盖掉某个内建函数,例如说 open(),那你得将其定义从另一个模组载 入。参考 Overriding Builtin Functions。在 Class/Template里面也有个范例。

如果你要覆盖掉一个 Perl运算子,像是 +或 ** ,那你该使用 use overload这个编译器指挥模组(pragma),其文件在 overload 。

如果你要覆盖父类别 (parent class)里的方法呼叫 (method calls),请看 Overridden Methods 。


--------------------------------------------------------------------------------

用 &foo和 foo()的方式来呼叫一个函数有什麽不同?
当你用 &foo的方式呼叫一个函数时,你等於让这个函数撷取你目前 @_里面的值,同时也跳过原型定义 (prototypes)不用。这表式此函数抓到的是你当时的 @_, 而非一个空的 @_!虽然严格讲起来它也不能算是个 bug (但是在 perlsub里面是这麽说的)但在大部份情况下,这也算不上是个特别功能。

当你用 &foo()的方式呼叫你的函数时,你会得到一个新的 @_,但是原型定义 仍然会被避开不用。

在一般情况下,你该用 foo()的方式去呼叫函数。只有在编译器已事先知道这 个函数的定义时,括号才能省略,譬如当这个函数所在的模组或包裹被 use (但如果是被 require则不行)时,或是透过先前提及或 use subs宣告等 方法,让编译器先接触到这个函数的定义。用这种呼叫方式,即使是当括号省掉时, 你都会得到一个乾净的 @_,不会有任何不该出现的旧值残留在上面。


--------------------------------------------------------------------------------

我如何作一个 switch或 case叙述?
这个问题在 perlsyn 文件里有更详尽的解释。简单来说,因为 Perl本身已提供了多种不同的条件测试方法可供使用 (数值比较、字串比较、 glob比较、正规表示式 对应、覆盖比较,及其它),所以并没有正式的 case叙述语法。虽然自 perl1起这就一直是许多人期盼的一个项目,但因 Larry无法决定怎样才是呈现这功能的最好方法,因此还是将它略掉。

下面这个简单的 switch范例以模式对应为基础。我们将要做的是对储存在 $whatchamacallit里面的参考值 (reference)的类型进行多重条件的判断。【译注:$whatchamacallit 函意为 $what_you_might_call_it】

SWITCH:
for (ref $whatchamacallit) {

/^$/ && die '不是个参考值';

/SCALAR/ && do {
print_scalar($$ref);
last SWITCH;
};

/ARRAY/ && do {
print_array(@$ref);
last SWITCH;
};

/HASH/ && do {
print_hash(%$ref);
last SWITCH;
};

/CODE/ && do {
warn '无法印出函数的 ref';
last SWITCH;
};

# DEFAULT

warn '跳过使用者自定的类型';

}


--------------------------------------------------------------------------------

我如何抓到呼叫未定义变数/函数/方法的事件?
在 Autoloading 和 AUTOLOAD: Proxy Methods里 提到的AUTOLOAD 方法让你能捕捉对於未定义函数与方法的呼叫。

如果是要处理一些在 -w之下触发警告讯息的未定义变数,你可以使用一个处理元 (handler)来捕捉 __WARN__这个虚拟信号 (pseudo-signal),范例如下:

$SIG{__WARN__} = sub {

for ( $_[0] ) {

/Use of uninitialized value/ && do {
#将警讯提升为致命行动
die $_;
};

#其它要捕捉的状况可以写在此。

warn $_;
}

};


--------------------------------------------------------------------------------

为什麽我的程式会找不到放在同一档案中的方法 (method)呢?
一些可能的原因:你用的继承给搞混了、你拼错了该方法的名字,或是物件的类别 错误。这些事在 perltoot里都有更详尽的说明。同时你也可以用 print ref($object) 来找出 $object这个物件是被归到哪个类别底下。

另一个可能的原因是你在 Perl还不知道这个包裹 (package)存在之前便将某个 类别名称在间接式物件语法中使用 (例如 find Guru "Samy" 。最好是在开始使用你的包裹前,先确定都已经先把它们定义好了,如果你用的是 use 而非 require的话,这件事便会自动处理好。不然的话,确定你使用箭头式语法 (例如,Guru-find(``Samy'')> 。在perlobj 里面对於物件的记号有详尽解释。


--------------------------------------------------------------------------------

我如何找出目前所在的 package为何?
如果只是一个随意的程式的话,你可以用下面的方法找出目前正被编译的包裹为何:

my $packname = ref bless [];

但如果是一个方法的话,而且印出的错误讯息中要包含呼叫此方法的物件 (不见得 就是把这个方法编译进去的那个物件)则:

sub amethod {
my $self = shift;
my $class = ref($self) || $self;
warn "我是被 $class这个物件所召唤";
}


--------------------------------------------------------------------------------

我如何将一大块 perl程式码变成注解?
用内嵌 POD格式的方法把程式码变注解:

#这是程式

=for nobody
这段就变成了注解

#程式继续下去

=begin comment text

接下来此处所有

的文字都会被
所有人忽略

=end comment text

=cut


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值