目录
▶︎
all
running…
- 前言
- 第1章 简介
- 1.1 获取帮助的方式
- 1.2 strict和warnings
- 1.3 程序版本
- 1.4 书单
- 第2章 使用模块
- 2.1 CPAN
- 2.2. 阅读模块手册
- 2.3 功能接口
- 2.4 面向对象的接口
- 2.5 核心模块内容
- 2.6 通过CPAN安装模块
- 2.7 搜索路径
- 2.8 在程序外部设置搜索路径
- 2.9 local::lib
- 第3章 中级基础
- 3.1 使用grep过滤列表
- 3.2 使用map转换列表
- 3.3 使用eval捕获错误
- 3.4 使用eval动态编译代码
- 3.5 使用do语句块
- 3.6 require
- 第4章 引用简介
- 4.1 在多个数组上完成相同的任务
- 4.2 Perl图形结构(PeGS)
- 4.3 数组引用
- 4.4 嵌套的数据结构
- 4.5 用箭头简化嵌套元素的引用
- 4.6 散列的引用
- 4.7 数组与散列的嵌套引用
- 4.8 检查引用类型
- 第5章 引用和作用域
- 5.1 循环引用造成内存泄露
- 5.2 匿名数组和散列
- 5.3 自动带入
- 第6章 操作复杂的数据结构
- 6.1 使用调试器
- 6.2 使用 Data::Dumper 模块查看复杂数据
- 6.4 数据编组
- 第7章 对子例程的引用
- 7.1 引用子例程
- 7.2 闭包
- 第8章 文件句柄引用
- 8.1 typeglob
- 8.2 标量
- 8.3 指向字符串
- 8.4 IO::Handle
- 8.5 IO::File
- 8.6 IO::Tee
- 8.7 IO::Pipe
- 8.8 IO::Null
- 8.9 IO::Dir
- 9 正则表达式引用
- 第10章 使用的引用技巧
- 10.1 施瓦茨变换
- 10.2 递归定义的数据
- 10.3 避免递归
- 第11章 构建更大型的程序
- 11.1 基本概念
- 11.2 嵌入代码
- 11.3 命名空间
- 第12章 创建你自己的发行版本
- 12.1 构建工具
- 12.2 Build.PL
- 12.3 Makefile.PL
- 12.3 添加额外的模块
- 12.4 目录文件介绍
- 12.5 文档
- 12.5.1 段落
- 12.5.2 文本格式
- 第13章 对象简介
- 13.1 调用方法
- 13.2 继承
- 13.3 调用父类方法
- 第14章 测试简介
- 第15章 带数据的对象
- 第x章 环境变量汇总
- 第x章 模块汇总
- 第x章 问题汇总
前言
- Perl版本:v5.14
第1章 简介
1.1 获取帮助的方式
- Stack Overflow
- Perlmonks:在线Perl社区。需要
- perl学习站点
1.2 strict和warnings
- 所有代码都应该打开
strict
和warnings
,以规范编写的perl代码,如: #!/usr/local/bin/perl use strict; use warnings;
1.3 程序版本
- 告知程序版本可以避免后续使用新版本的Perl时,会因为新加入的特性导致程序无法正常工作。
- 方法:
use 5.014
use v5.14.2
use 5.14.2
1.4 书单
- 有助于编写可复用代码:《Perl Best Practices》- Damian Conway
第2章 使用模块
2.1 CPAN
- CPAN是Perl的杀手锏,有各种信息、模块和服务:
- CPAN主页:http://www.cpan.org
- CPAN搜索服务页面:http://search.cpan.org
- MetaCPAN:https://www.metacpan.org
- CPAN的下一代搜索页面
- CPAN Testers:http://cpantesters.org
- 作者上传至CPAN的每一个模块都会被自动测试,并将报告上传到此。
- CPANdeps:http://deps.cpantesters.org
- Tester的进一步补充,合并了依赖性信息。
- CPAN RT:http://rt.cpan.org
- 问题跟踪系统
2.2. 阅读模块手册
- 使用
perldoc
来阅读模块文档,Unix则用man
也可以,如: perldoc File::Basename # On Unix man File::Basename
2.3 功能接口
- 加载模块:
use File::Basename;
- 这样会把模块的默认符号加载到当前脚本中。
- 注意:如果当前脚本中已经有该符号则会被覆盖。
- 选择需要导入的内容,仅导入fileparse和basename: # fool way use File::Basename ('fileparse', 'basename'); # good way use File::Basename qw( fileparse basename ); # full namespace my dirname = File::Basename::dirname(some_path); use File::Basename (); # no import 空列表特指不导入任何符号。
2.4 面向对象的接口
代码语言:javascript复制use File::Spec;
my $filespec = File::Spec->catfile( $homedir{homqyy}, 'web_docs', 'photos', 'USS_Minnow.gif');
2.5 核心模块内容
-
Module::CoreList
是一个数据结构和接口库,该模块把很多关于Perl 5版本的模块历史信息聚合在一起,并且提供一个可编程的方式以访问它们: use Module::CoreList; # 查看在perlv5.01400中的CPAN版本 print $Module::CoreList::version{5.01400}{CPAN}; - 也可以在bash中直接运行命令
corelist
:% corelist Module::Build
2.6 通过CPAN安装模块
- 自动安装:
- 使用perl自带的
cpan
工具安装:% cpan Perl::Critic
- 使用
cpanp
(CPAN Plus):% cpanp -i Perl::Tidy
- 使用
cpanm
(CPAN Minus):% cpanm DBI WWW::Mechanize
- 零配置,轻量级的CPAN客户端
- 使用perl自带的
- 手动安装:
- Makefile.PL:
- 下载perl模块包:
% wget <URL>
(该URL可以从CPAN站点中获取) - 解压perl模块包:
% tar -xzf <MODULE.tar.gz>
- 进入模块目录:
% cd <MODULE>
% perl Makefile.PL
- 可以用
INSTALL_BASE
参数来指定安装的路径:perl Makefile.PL INSTALL_BASE=/home/homqyy/
- 可以用
% make
% make test
% make install
- 下载perl模块包:
- Build.PL
- 下载perl模块包:
% wget <URL>
(该URL可以从CPAN站点中获取) - 解压perl模块包:
% tar -xzf <MODULE.tar.gz>
- 进入模块目录:
% cd <MODULE>
% perl Build.PL
- 可以用
--install_base
参数来指定安装路径:% perl Build.PL --install_base /home/homqyy/
- 可以用
% perl Build
% perl Build test
% perl Build install
- 下载perl模块包:
- Makefile.PL:
2.7 搜索路径
- perl是通过
@INC
数组里的路径去搜索模块的,可以通过以下两种方式获取@INC
的值:% perl -V
% perl -le "print for @INC
- 程序中添加指定路径:
BEGIN { unshift @INC, '/home/homqyy/lib'; }
use lib '/home/homqyy/lib'; # good way. Action the same to above example
use constant LIB_DIR => '/home/homqyy/lib'; # set a constant use lib LIB_DIR;
- 推荐使用
use lib
,它是在编译执行,行为等效于首个例子:将参数给的路径加入到@INC
数组的开头。 use constant
可以设置常量,也是在编译时运行。
- 推荐使用
- 上述的指定路径是硬编码,不具备可移植性,我们可以利用FinBin模块来实现更通用的添加路径: use FindBin qw(Bin); # Bin is path of the current script use lib Bin; # join path of the script to @INC use lib "Bin/../lib"; #join "
2.8 在程序外部设置搜索路径
- 使用环境变量
PERL5LIB
:% export PER5LIB=/home/homqyy/lib:/usr/local/lib/perl5
- 设立
PERL5LIB
环境变量的目的是为了给非管理员用户也能够扩展Perl的安装路径,如果管理员想增加额外的安装目录,只需要重新编译并安装Perl即可。 - 也可以在程序运行的使用通过
-I
选项来扩展安装路径:% perl -I/home/homqyy/lib test.pl
2.9 local::lib
- 在没有管理员权限的时候,我们需要有个便携的安装路径以及自动找到路径的方法,这时候就可以使用
local::lib
。 - 安装
- 该模块还不是核心模块,需要用
cpan
下载:% cpan local::lib
- 该模块还不是核心模块,需要用
- 查看提供的环境变量:
% perl -Mlocal::lib
: - 使用其安装模块:
- 对于
cpan
:% cpan -I Set::Crossproduct
- 对于
cpanm
:% cpanm --local-lib HTML::Parser
- 对于
- 在脚本中自动将安装的路径加载到
@INC
中: use local::lib; - 自定义路径: % perl -Mlocal::lib='~/perlstuff'
第3章 中级基础
- 常见列表操作符:
print
:打印sort
:正序排列reverse
:反序排列push
:添加元素
3.1 使用grep过滤列表
- _ > 10, @input_numbers; # filter number of greater than 10 my sum; sum = _ for @digits; sum % 2; } # 4. block of code my @odd_digit_sum = grep { my input = _; my @digits = split //, sum; sum = _ for @digits; sum; sum = _ for split //; sum % 2; } @input_numbers; 这里的 _ 是列表中的每个元素值,而且是别名,即:如果修改了 _ 值,则原值也将被修改。 代码快实际上是一个匿名子例程。 将示例3用示例4的代码块代替时,有两处需要变更: 不再使用入参,而是
3.2 使用map转换列表
- 功能是将列表中的元素转换成另一个(列表上下文)。与grep一样,支持表达式和代码块。 my @input_numbers = (1, 2, 4, 8, 16, 32, 64); my @result = map $_ 100, @input_numbers; # @result = (101, 102, 104, 108, 116, 132, 164)
- 因为是列表上下文,因此一个元素可以产生多个结果: my @input_numbers = (1, 2, 4, 8, 16, 32, 64); my @result = map { _, 3 * _ } @input_numbers; # @result = (1, 3, 2, 6, 4, 12, 8, 24, 16, 48, 32, 96, 64, 192)
- 列表转hash:当列表成对时,可以将其转成hash,列表会被按'Key-Value'解析: my %hash = @result; # 或则直接用 map my %hash = map { _, 3 * _ } @input_numbers; 有时候我们不关心键值,只关心是否有键存在,这时候可以如此: my %hash = map { person = 'Gilligan'; if ( hash{person} ) { print "
- 可以返回一个空列表
()
来表示此次结果不记录到列表中,即删除列表中的某个元素 my @result = map { my @digits = split //, $_; if ($digits[-1] == 4) { @digits; } else { # 不处理非4结尾的数据 (); } } @input_numbers;- 因此,利用此特性我们可以用
map
来代替grep
- 因此,利用此特性我们可以用
3.3 使用eval捕获错误
- 使用 eval 来捕获错误,避免程序因为出错直接崩溃。如果捕获到错误,则 @ 会有值,反之则为空。最常见的用法就是在 eval之后立刻判断 @ 的值: eval { average = total / count }; print "Continuing after error:
-
eval
语句块后的分号是必须的,因为它是一个术语,语句块是真实的语句块,而不是像if
和while
。 -
eval
语句块中可以包含my
等任意语句。 - eval 语句块有类似子例程的返回值(最后一行表达式求值,或者之前通过 return 返回的值)。如果块中代码运行失败,在标量上下文中返回 undef ,在列表上下文中返回空列表 (): my average = eval { total /
-
eval
语句块不能捕获最严重的错误:使perl自己中断的错误。 - 可以使用 Try::Tiny 来处理复杂的异常: use Try::Tiny; my average = try { total /
3.4 使用eval动态编译代码
- operator ( qw( - * /) ) { my result = eval "2 operator 2 is resultn"; }
3.5 使用do语句块
-
do
将一组语句汇聚成单个表达式,其执行结果为最后一个表达式的值。do
非常适合创建一个操作的作用域: my $file_contents = do { local $/; local @ARGV = ( $filename ); <> }; -
do
还支持字符串参数的形式: do $filename;do
语句查找文件并读取该文件,然后切换内容为eval
语句块的字符串形式,以执行它。- 因此
do
将忽视文件中的任何错误,程序将继续执行。
3.6 require
- 除了
use
也可以用require
来加载模块,实际use
等效于以下代码: BEGIN { require List::Util; List::Util->import(...); }- 但是
require
实际上是在运行程序时执行的,不过require
可以记录所加载的文件,避免重复加载同样的文件。
- 但是
第4章 引用简介
- 这里的引用,效果类似指针,但与指针不同的是这里指向的是整个数组,而不是首个元素。
4.1 在多个数组上完成相同的任务
- item (@required) { unless ( whos_items{item} ) { # not found in list? print "who is missing item.n"; } } } my @skipper = qw(blue_shirt hat jacket preserver sunscreen); my @professor = qw(sunscreen water_bottle slide_rule batteries radio); check_required_items('skipper', @skipper); check_required_items('professor', @professor); ▶︎allrunning…
- 上述示例在将数组的值传递给了方法
check_required_items
,如果值大量的话势必会造成大规模的复制数据,浪费空间并损耗性能。
4.2 Perl图形结构(PeGS)
- 该图形由Joseph Hall开发,用图形可以方便的解释Perl,图形内容不做记录。
4.3 数组引用
- ref_to_skipper = @skipper; my second_fef_to_skipper = reference_to_skipper;
- items = shift; # 获取引用值 my %whose_items = map { _, 1 } @items; # 解引用 my @required = qw(preserver sunscreen water_bottle jacket); for my item (@required) { unless ( whose_items{ item } ) # 没有在列表中发现该工具 { print "who is missing item.n"; push @missing, item; } } if (@missing) { print "Adding @missing to @
- itmes } 如果引用项是简单的 裸字,则可以省略掉中间的括号:@items
4.4 嵌套的数据结构
- 假设我们有如下嵌套结构: my @skipper = qw(blue_shirt hat jacket preserver sunscreen); my @skipper_with_name = ('Skipper' => @skipper); my @all_with_names = { @skipper_with_name, # ...其他元素 }
- all_with_names[0] } 进行解引用可以得到带有两个元素的数组 @skipper_with_name 的引用。接着可以用如下方式获取名字和其携带的工具: my who = { all_with_names[0] }[0] my skipper = { person (@all_with_names) { my who = person[0]; my provisions_reference = person[1]; check_required_items(who, provisions_reference); }
4.5 用箭头简化嵌套元素的引用
- 学过C语言的都知道->具有解引用的效果,而Perl也支持类似的方式: all_with_names[0]->[0]all_with_names[0] }[0][1] 等效于 all_with_names[0]->[0][1]
4.6 散列的引用
- hash_ref = %gilligan_info; # 引用散列 # 获取名称 name = { hash_ref }{'name'}; # 带括号的形式name1 = $hash_ref{'name'} # 不带括号的形式name2 =
4.7 数组与散列的嵌套引用
- 结合4.5和4.6即可,比如: my %gilligan_info = { name => 'Gilligan', hat => 'White', shirt => 'Red', position => 'First Mate', }; my %skipper_info = { name => 'Skipper', hat => 'Black', shirt => 'Blue', position => 'Captain', }; my @crew = (%gilligan_info, %skipper_info); # 数组元素引用了散列
- 可以如下使用各值:
- 选用一个我喜欢的方式:
$crew[0]->{'name'}
- 选用一个我喜欢的方式:
4.8 检查引用类型
- hash_ref = shift; my ref_type = ref hash_ref; croak "I expected a hash reference!" unless ref_type eq 'HASH'; while (my (key, value) = each %hash_ref ) { print "
- hash_ref = shift; my ref_type = ref hash_ref; croak "I expected a hash reference!" unless ref_type eq 'HASH'; foreach my key ( %hash_ref ) { # ...其他 } }
- 也可以用
eval
来检查: sub is_hash_ref { my $hash_ref = shift; return eval { keys %$ref_type; 1 }; }
第5章 引用和作用域
- Perl通过“引用计数”的来实现“自动回收垃圾”的机制。即,一块数据仅当引用计数为0时被销毁,且被销毁的数据空间通常并不会返还给操作系统,而是保留给下一次需要空间的数据使用。
- 每创建一个数据的时候,引用计数值初始为1。
5.1 循环引用造成内存泄露
- 示例: { my @data1 = qw(one won); my @data2 = qw(two too to); # 创建循环引用 push @data2, @data1; # @data2引用@data1,'qw(one won)'的引用数变成2 push @data1, @data2; # @data1引用@data2,'qw(two too to)'的引用数变成2 } # 由于@data1和@data2超出作用域,因此引用计数分别减1,但是引用值仍旧不为0,内存泄露!
- 使用引用计数在循环引用的情况下无法正常处理,因为它的引用计数将永远不为0:如例子,
@data1
和@data2
结束生命周期后,两个列表的引用计数都还为1。 - 因此,我们必须谨防创建循环引用,或则在不得不这样做的时候,在变量超出作用于之前打断“环”: { my @data1 = qw(one won); my @data2 = qw(two too to); # 创建循环引用 push @data2, @data1; # @data2引用@data1,'qw(one won)'的引用数变成2 push @data1, @data2; # @data1引用@data2,'qw(two too to)'的引用数变成2 # 打破环 @data1 = (); # 解除对@data2的引用,'qw(one won)'引用数减1,剩下1 @data2 = (); # 解除对@data2的引用,'qw(two too to)'引用数减1,剩下1 } # 由于@data1和@data2超出作用于,因此引用计数从1减为0,回收数据空间
5.2 匿名数组和散列
- 匿名数组使用
[]
创建,匿名散列由{}
创建: # 匿名数组 my $array_ref = ['one', 'two']; # 匿名散列 my $hash_ref = { one => '1', two => '2', }; - 由于匿名散列与代码块有冲突,因此我们可以在左括号前加入一个
;
来显示表示是一个代码块: { 'one' => 1, 'two' => 2, } # 这是一个匿名散列 {; push @array, '1'; } # 这是一个代码块
5.3 自动带入
- 如果没有给变量(或者访问数组或者散列中的单个元素)赋值,Perl将自动创建代码过程假定存在的引用类型。
- not_yet; # 未定义的变量 @not_yet = (1, 2, 3); # 由于我们将not_yet当成了数组的引用使用,因此Perl自动作了'not_yet2 = ( 'one' => 1, 'two' => 2, ); # 由于我们将top;top->[2]->[4] = 'lee-lou'; # 同样的,这里将沿途自动创建对应的匿名数组
第6章 操作复杂的数据结构
6.1 使用调试器
- 在运行程序时添加
-d
参数来启动调试模式,类似于C程序的gdb
:% perl -d ./test.pl
- 使用方法可以查阅手册
perldebug
,或则在通过-d
启动后输入h
来查看帮助。
6.2 使用 Data::Dumper 模块查看复杂数据
- 该模块提供了一个基本方法,将Perl的数据结构显示为Perl代码:
use Data::Dumper;
my %total_bytes;
while (<>)
{
my ($source, $destination, $bytes) = split;
$total_bytes{$source}{$destination} = $bytes;
}
print Dumper(%total_bytes)
- 输入文件为:
professor.hut gilligan.crew.hut 1250
professor.hut lovey.howell.hut 910
thurston.howell.hut lovey.howell.hut 1250
professor.hut lovey.howell.hut 450
ginger.girl.hut professor.hut 1218
ginger.girl.hut maryann.girl.hut 199
- 结果如下所示:
$VAR1 = {
'thurston.howell.hut' => {
'lovey.howell.hut' => 1250
},
'professor.hut' => {
'lovey.howell.hut' => 1360,
'gilligan.crew.hut' => 1250
},
'ginger.girl.hut' => {
'maryann.girl.hut' => 199,
'professor.hut' => 1218
}
};
- 该模块目的是用于后续重建数据结构使用,因此输出结构就是一段完成的Perl代码
- 其他转储程序
-
Data::Dump
: use Data::Dump qw(dump); dump( %total_bytes ); -
Data::Printer
use Data::Printer; p( %total_bytes );
-
6.4 数据编组
- 数据编组:利用
Data::Dumper
可以将复杂的数据结构转化为字节流,这样可以供另一个程序使用。 - 因为
Data::Dumper
输出的符号将变成普通的VAR符号,这样会影响阅读,因此可以利用Dump
接口来实现符号的定义: print Data::Dumper->Dump( [@data1, @data2], [qw(*data1, *data2)] ); - 更适合编组的模块
Storable
:原因是其生成的更短小并且易于处理的文件:- 要求:必须把所有数据放入到一个引用中
- 使用方法1:内存
use Storable qw(freeze thaw);
use Data::Dumper;
my @data1 = qw(one won);
my @data2 = qw(two too to);
push @data2, @data1;
push @data1, @data2;
# 将'[@data1, @data2]'冻结到'$frozen'中
my $frozen = freeze [@data1, @data2];
# 解冻(恢复)
my $array_all_ref = thaw($frozen);
print Dumper($array_all_ref);
▶︎
all
running…
- 使用方法2:文件
use Storable qw(nstore retrieve);
use Data::Dumper;
my @data1 = qw(one won);
my @data2 = qw(two too to);
push @data2, @data1;
push @data1, @data2;
# 将'[@data1, @data2]'冻结到文件中
nstore [@data1, @data2], './output/array.db';
# 从文件中恢复
my $array_all_ref = retrieve './output/array.db';
print Dumper($array_all_ref);
▶︎
all
running…
- 浅复制和深复制:平时使用的
my @d1 = @d2
是浅拷贝,而Storable
提供了深拷贝的方法:my @d1 = @{ dclone @d2 }
- YAML模块:通过该模块可以让被
Data::Dumper
编组后的数据可读性更强 - JSON模块:提供了将数据结构与JSON格式间相互转换的方法
第7章 对子例程的引用
7.1 引用子例程
- 与数组和散列引用一样,也是用
进行引用,比如: my $ref_to_greeter = &skipper_greets; # '&'是函数
- 解引用也是有3种: # 1 大括号 &{ $ref_to_greeter } ('Gilligan'); # 2 只由简单的符号和裸字时可以省略大括号 &$ref_to_greeter('Gilligan'); # 3 用'->' $ref_to_greeter->('Gilligan');
- 匿名子例程,格式为:
sub { ...body of subroutine };
,结果是创建了一个匿名函数的引用,比如: my $ginger = sub { my $person = shift; print "Ginger: (in a sultry voice) Well hello, $person!n" }; $ginger->('Skipper'); ▶︎ all running… - 回调函数:通过传递一个函数的引用形成回调,比如: use File::Find; my @starting_directories = qw(.); find( sub { print "$File::Find::name foundn"; }, # 回调函数,每搜索到一个结果都会被调用 @starting_directories, # 搜索的目录 ); ▶︎ all running…
7.2 闭包
- count = 0; return sub { print count, ": count',因此'count'的引用数为2 } my callback = create_find_callback_that_counts(); find(callback, '.'); # 'count'的引用数仍为1,不会被销毁回收 ▶︎allrunning…
- total_size = 0; return (sub { total_size = -s if -f }, sub { return total_size }); } my (count_em, get_results) = create_find_callbacks_that_sum_the_size(); find(count_em, '.'); my total_size = &
- 也可以通过参数来初始化闭包变量
- countdown赋值为10 因此,以下代码将不能正常工作: sub count_down { countdown--; # 此时countdown还未定义 }; my _, 0 } @castaways; # 编译错误 tab{castaway} ; print "castaway: tab{
- 转储闭包:使用
Data::Dump::Streamer
模块可以将代码引用和闭包的内容展示出来 use Data::Dump::Streamer; my @luxuries = qw(Diamonds Furs Caviar); my $hash = { gilligan => sub { say 'Howdy Skipper!' }, Skipper => sub { say 'Gilligan!!!!' }, 'Mr. Howell' => sub { say 'Money money money!' }, Ginger => sub { say $luxuries[rand @luxuries] }, }; Dump $hash; ▶︎ all running…
第8章 文件句柄引用
- 在 Perl v5.6 及后续版本,
open
支持打开匿名的临时文件: # 文件名设置为'undef' open my $fh, ' >', undef or die "Could not open temp file: $!";
8.1 typeglob
- 在旧版本上,使用符号表(是typeglob,书籍翻译成符号表有点不好理解,因为还有个symbol table)来传递文件句柄: open LOG_FH, '>>', 'castaways.log' or die "Could not open castaways.log: $!"; log_message(*LOG_FH, 'The Globetrotters are stranded with us!'); sub log_message { local *FH = shift; # 包变量不允许使用词法变量'my',这里使用'local' print FH @_, "n"; }
8.2 标量
- 从Perl v5.6开始,open能够用标量来存储句柄了,前提是该变量的值必须是
undef
- 建议在文件句柄部分加上大括号,以显示声明我们的意图
- 当标量超出作用域后Perl将自动关闭对应的文件句柄,可以不显示的关闭
- 如果想在老版本中使用标量,则可通过模块
IO::Scalar
来实现。 - 示例: open my $log_fh, '>>', 'castaways.log' or die "Could not open castaways.log: $!"; print {$log_fh} "We have no bananas today!n"; while (<>) { print {$log_fh}; # 用花括号包裹文件句柄 }
8.3 指向字符串
- 从Perl v5.6开始,能够以文件句柄的形式打开一个标量而不是文件: open my string_fh, '>', my
- multiline_string = "data1ndata2ndata3n"; open my
8.4 IO::Handle
- 将文件句柄以对象的形式使用: use IO::Handle; open my fh, '>', fh->print('Coconut headphones');
8.5 IO::File
- 使用该模块以一个更友好的方式来使用文件句柄: use IO::File; # 打开一个文件:与C语言的fopen类似 my $read_fh = IO::File->new('castaways.log', 'r'); my $append_fh = IO::File->new('castaways.log', O_WRONLY|O_APPEND); # 创建临时文件 my $temp_fh = IO::File->new_tmpfile;
8.6 IO::Tee
- 多路通道输出模块,该模块功能等效于shell工具:
tee
8.7 IO::Pipe
- pipe = IO::Pipe->new;
8.8 IO::Null
- Debug = 1; my debug_fh = Debug ? *STDOUT : IO::Null->new;
8.9 IO::Dir
- 用该模块去操作目录
9 正则表达式引用
- 预编译操作符:
qr//
- 如果用单引号
'
作为分隔符(qr''
),则Perl解释器就不会做任何双引号插入操作:qr'$var'
- 正则表达式选项:
- 可以用3种方式添加选项(
flags
):- 在匹配或替换操作符最后一个分隔符后面添加:
m/pattern/flags
或s/pattern/flags
- 在qr后面添加:
qr/pattern/flags
- 在模式本身中指定:
?flags:pattern
- 能够在模式中的flag前面追加一个-号表明要移除某个修饰符:
qr/abc(?x-i:G i l l i g a n)def/i
,使用了x
,移除了i
- 能够在模式中的flag前面追加一个-号表明要移除某个修饰符:
- 在匹配或替换操作符最后一个分隔符后面添加:
- 可以用3种方式添加选项(
- regex = qr/Gilligan/; print "variable of regexn" if string =~ regex; print "smartmatchn" if string ~~ regex; string =~ s/regex/Skipper/; print "after modify: pattern (@patterns) { if (name =~ pattern) { say "Manual Match!"; last; } } # 智能匹配将遍历数组中的每个元素 say "Smart Match!" if
- 当在一个更大的模式中引用正则表达式时,正则的引用其相当于一个原子(原理是qr操作的pattern会自动加上非捕获圆括号(?:):my poor_people = qr/r1|
- digit = qr/d/; my alphadigit = qr/(?i:alpha|digit)/; my safe = qr/[_. -]/; my reserved = qr|[;/?:@&=]|; my escape = qr/%hexhex/; my unreserved = qr/alpha|digit|safe|extra/; my uchar = qr/unreserved|escape/; my xchar = qr/unreserved|reserved|escape/; my ucharplus = qr/(?:uchar|[;?&=])*/; my digits = qr/(?:digit){1,}/; my hsegment = qr/ucharplus;/; my hpath = qr|hsegment(?:/hsegment)*|; my search = ucharplus; my scheme = qr|(?i:https?://)|; my port = qr/digits/; my password = ucharplus; my user = ucharplus; my toplevel = qr/alpha|alpha(?:alphadigit|-)*alphadigit/; my domainlabel = qr/alphadigit|alphadigit(?:alphadigit|-)*alphadigit/; my hostname = qr/(?:domainlabel.)*toplevel/; my hostnumber = qr/digits.digits.digits.digits/; my host = qr/hostname|hostnumber/; my hostport = qr/host(?::port)?/; my login = qr/(?:user(?::password)@)?/; my urlpath = qr/(?:(?:xchar)*)/;
- 使用
Regexp::Common
模块来直接获取某个pattern。 -
egexp::Assemble
模块帮助我们建立高效的择一匹配 - List::Util模块中的first函数功能类似grep,但是它只要成功命中一次就停止继续匹配。 my ( math ) = fist { name =~ patterns{_} } keys %patterns;
第10章 使用的引用技巧
10.1 施瓦茨变换
- 一个高效的排序结构: my @output_data = map { EXTRACTION }, # 提取 sort { COMPARISON } # 比较,如果是哈希形式的话,可以如此使用:{ a->{xxx} cmp
- _->[0], sort { _, ask_monkey_about(_) ], @castaways map _->{VALUE}, sort { a->{ID} <=> a->{NAME} AND
10.2 递归定义的数据
- 在递归算法的不同分支上拥有多个基线条件是很常见的。没有基线条件的递归算法将是无限循环。
- 递归子例程有一个调用它本身的分支用于处理部分任务,以及一个不调用它本身的分支用于处理基线条件。
- 注意:类似Perl的动态语言无法自动将“尾递归”转为循环,因为再一次调用子例程之前,子例程定义可能改变。
use Data::Dumper;
sub data_for_path {
my $path = shift;
if (-f $path or -l $path) # files or symbolic links
{
return undef;
}
if (-d $path)
{
my %directory;
opendir PATH, $path or die "Cannot opendir $path: $!";
my @names = readdir PATH;
closedir PATH;
for my $name (@names)
{
next if $name eq '.' or $name eq '..';
$directory{$name} = data_for_path("$path/$name");
}
return %directory;
}
warn "$path is neither a file nor a directoryn";
return undef;
}
print Dumper(&data_for_path('../'));
▶︎
all
running…
10.3 避免递归
- data = {}; my @queue = ( [start, data] ); while ( my
- 深度优先解决方案:FIFO
- 优势:能够在任意喜欢的层级很轻易地停留。
- 广度优先解决方案:LIFO
第11章 构建更大型的程序
11.1 基本概念
- 函数获取参数的方法:
my $arg = shift
:作者更喜欢这种(my $arg) = @_
:与lua风格相似
-
.pm
扩展名是“Perl模块”的意思
11.2 嵌入代码
- 用eval嵌入代码:eval code_string; die @ if
- 用do嵌入代码:do 'Navigation.pm'; die @ if @; 导入的代码作用域在do自己里面,因此类似my等语句并不会影响主程序。 不会搜索模块目录,因此需要提供绝对路径或相对路径。
- 用require嵌入代码:追踪文件,可以避免重复 导入文件中的任何语法错误都将终止程序,所以不再需要很多die @ if @语句; 文件中的最后一个求值表达式必须返回一个真值,因此require语句才能知道该文件正确导入 由于这个特点,用于require的文件在末尾都需要加个神秘的1
11.3 命名空间
- 命名空间可以避免符号冲突。
- 对于
.pm
的文件,在文件开头增加命名空间:package Navigation;
。 - 命名规则与变量一样,包名应当以一个大写字母开头(来源于
perlmodlib
文档) - 包名也可以由
::
(双冒号)分隔多个名称:Name1::Name2
- 主程序的包名为
main
- Package有作用域:
- 代码块: package Navigation; { # start scope block package main; # now in package main sub turn_toward_heading { # main::turn_toward_heading ... code here ... } } # end scope block # back to package Navigation sub turn_toward_heading { # Navigation::turn_toward_heading ... code here ... }
- Perl v5.12后支持包语句块: package Navigation { ... code here ... }
- 无论当前包如何定义,有些名称或变量总在main包中: 名称:ARGV, ARGVOUT, ENV, INC, SIG, STDERR, STDIN, STDOUT 标点符号变量:_, 2等
- 设置包版本的方法: 设置VERSION的值:our VERSION = '0.01' 在v5.12版本后可以用package指定:package Navigation 0.01
第12章 创建你自己的发行版本
- 构建方法有两种:
- Makefil.PL:老的,基于
make
,使用ExtUtils::Maker
构建 - Build.PL:新的,存Perl工具,使用
Module::Build
构建
- Makefil.PL:老的,基于
12.1 构建工具
- h2xs
- Module::Starter
- 创建模板:Module::Starter::Plugin
- Dist::Zilla:这个模块不但可以自动创建发行版,而且在我们修改发行版中的文件后,它还知道如何更新发行包。
12.2 Build.PL
- 创建构建框架:
% module-starter --module=Animal --author="yourName" --email="yourEmail" --verbose --mb
- 创建构建脚本:
% perl Build.PL
- 开始构建:
% ./Build
- 执行测试:
% ./Build test
- 发行前检测一下内容是否有遗漏:
% ./Build disttest
- 发行版本:
% ./Build dist
12.3 Makefile.PL
- 创建构建框架:
% module-starter --module=Animal --author="homqyy" --email="youEmail" --verbose --builder="ExtUtils::Makemaker"
- 创建构建脚本:
% perl Makefile.PL
- 开始构建:
% make
- 执行测试:
% make test
- 发行前检测一下内容是否有遗漏:
% make disttest
- 发行版本:
% make dist
12.3 添加额外的模块
- 安装插件:
Module::Starter::AddModule
- 添加额外的模块:
module-starter --moudle=addon_module --dist=.
12.4 目录文件介绍
MANIFEST
:记录检查的结果META.*
文件描述了发行版本的信息,其中客户端最关系_require
相关字段。
12.5 文档
- pod语法:
perldoc pod
- 检测格式:
podchecker
- 查看或产生文档:
- 查看:
perldoc module.pm
- 产生文档:
pod2html
,pod2man
,pod2text
,pod2usage
- 查看:
12.5.1 段落
- 标题:
=head n
# 1级标题 =head1 NAME # 2级标题 =head2 DESCRIPTION # 3级标题 =head3 Functions # 返回代码模式 =cut - 有序列表: # 指明缩进空格数n:over n =over 4 =item 1. Gilligan =item 2. Skipper =item 3. Ginger # 结束列表 =back
- 无序列表 # 指明缩进空格数n:over n =over 4 =item * Gilligan =item * Skipper =item * Ginger # 结束列表 =back
- 文本: # 文本可以直接添加,无需任何标记 =head1 SYNOPSIS # 直接书写的文本会被自动换行 Quick summary of what the module does. Perhaps a little code snippet. # 如果不换行可以开启“逐字段落”:以空格开始的文本 use Animal; my $foo = Animal->new(); =cut
12.5.2 文本格式
- 每一种含格式都以一个大写字母开始,并且用
<
和>
括住所需的内容- B<粗体文本>
- C<代码文本>
- E<实体文本>
- I<斜体文本>
- L<链接文本>
- 根据需要,可以增加
<
和>
的个数,只要成对就行:B<<< 粗体文本 >>> - 使用utf8: =encoding utf8 文本内容
第13章 对象简介
- 面向对象编程(OOP)
- 对于Perl来说,仅当程序超过1000行时(经验值),OOP的溢出才能显露出来
- OOP书籍:
- 《Object Oriented Perl》Damian Conway(Manning出版社)
13.1 调用方法
- Class->method(@args)
- 这种调用方式,会隐式的添加类名作为首个参数,等效于
Class::method(Class, @args)
# 包名 use Cow; Cow->speek; # 变量 my $beast = 'Cow'; $beast->speek;
- 这种调用方式,会隐式的添加类名作为首个参数,等效于
- Class::Method('Class', @args)
- 该调用方法与
Class->method(@args)
等效
- 该调用方法与
13.2 继承
- 示例1: package Cow; use Animal; our @ISA = qw(Animal); # 这里表明继承了'Animal'这个类 sub sound { "mooo" }; # 这里重载了'Animal->sound'方法
- 示例2(等效示例1): use v5.10.1; package Cow; # 等效于 'use Animal;' 和 'ousr @ISA = qw(Animal);' use parent qw(Animal); sub sound { "mooo" };
- 使用类的方式调用函数时,Perl的调用过程为: 构建参数列表 先尝试查找Class::method 在@ISA中按序找,比如:ISA[0]::method、ISA[1]::method、… 调用找到的方法,并将1中保存的参数列表传入 首个参数是类名
-
@ISA
注意事项:@ISA
中查找都是递归的,深度优先,并且从左到右进行。
13.3 调用父类方法
- 直接调用(不提倡): package Mouse; use parent qw(Animal); sub sound { 'squeak' }; sub speak { my $class = shift; $class->Animal::speak; # 直接调用,这里无法保证Animal一定有speak,所以不是好方法。 print "[but you can barely hear it!]n"; }
- SUPER调用(提倡) package Mouse; use parent qw(Animal); sub sound { 'squeak' }; sub speak { my $class = shift; $class->SUPER::speak; # 通过SUPER调用,会自动递归的找 print "[but you can barely hear it!]n"; }
第14章 测试简介
- 测试模块:
- 基本模块:
Test::More
- 其他模块:
Test::*
- 基本模块:
- 测试文档:
Test::Turorial
- 声明测试数量 # 1. 在开头写明测试数量 use Test::More; plan tests => 1; # ... # 2. 或则在末尾声明测试结束 done_testing();
- 测试的艺术:
- 我们需要测试代码运行中断的情况,以及代码正常工作的情况。
- 需要测试边界和中间情况。
- 如果某种情况应当抛出异常,我们也要确保测试不会有不良的副作用:传递额外的参数或则多余的参数,或则没有传递足够的参数,搞混命名参数的大小写。
- 处理浮点数:
Test::Number::Delta
- 测试有两种模式,通过以下两个环境变量区分:
RELEASE_TESTING
:作者自行的测试,为发行前的准备AUTOMATED_TESTING
:自动测试,在用户侧进行的测试
- 模块编译检查:在
BEGIN
中使用use_ok()
#!perl -T use Test::More; plan tests => 1; BEGIN { use_ok( 'Animal' ) || print "Bail out!n"; } - 由于开启了“污染”检查模式(
perl -T
),因此PERL5LIB
这个环境变量会被忽略,需要自行指定搜索路径:- 使用
-I
指定:perl -Iblib/lib -T t/00-load.t
- 使用
blib
模块搜索:perl -Mblib -T t/00-load.t
- 使用
- 用
TODO
标注那些期望测试失败的用例,类似于备忘,该用例失败后不会作为失败处理。其中,$TODO
作为测试的标签: TODO: { local $TODO = "Need to replace the boilerplate text"; ... } - 测试Pod:当安装了
Test::Pos
和Test::Pos::Coverage
时,./Build test
会对Pod进行测试。 - 测量测试覆盖率:
- 安装模块
Devel::Cover
- 执行
% ./Build testcover
来进行覆盖率测量 - 输出报告:
% cover
- 安装模块
第15章 带数据的对象
第x章 环境变量汇总
- PERL5LIB:设置搜索路径
- Linux可用
:
分隔多个搜索路径 - Windows可用
;
分隔多个搜索路径
- Linux可用
第x章 模块汇总
- Cwd 提供了获取当前路径的方法
- Data::Dumper 数据编组:将Perl的数据结构转为Perl代码(字节流)
- Data::Dump
- Data::Printer
- File::Basename 处理路径
- File::Spec
类似于
File::Basename
,但是是面向对象的。 - File::Find 提供一个可移植的方式高效的遍历给定文件系统的层次结构
- List::Unit
- first: 与grep用法一样,只是匹配一次成功就返回
- Math::BigInt 能够处理超出Perl本身范围的数字
- 构建工具
- h2xs
- Module::Starter
- Module::Starter::Plugin 创建模板
- Dist::Zilla 这个模块不但可以自动创建发行版,而且在我们修改发行版中的文件后,它还知道如何更新发行包。
- Module::Starter 一个好用的构建发行版本的模块,支持插件
- Regexp::Common
- Abigail,Perl的一位正则表达式大事,将大部分复杂的模式放入一个模块中
- 该模块使用了tie,详情可以查看perltie文档
- Regexp::Assemble 该模块帮助建立高效的择一匹配
- Spreadsheet::WriteExcel 创建并写入Excel数据
- HTTP::SimpleLinkChecker URL检测
- Try::Tiny 异常处理模块
- Storable 数据编组:将Perl的数据结构转为二进制流,并且提供了深拷贝
- IO::Handle
- Pler实际上使用该模块实现文件句柄操作,因此,文件句柄标量实际上是
IO::Handler
模块的对象。 - 自 Perl v5.14 之后,不必显示加载 IO::Handler模块
- Pler实际上使用该模块实现文件句柄操作,因此,文件句柄标量实际上是
- IO::File
该模块是
IO::Handle
模块用于操作文件的子集。属于标准发型版本。 - IO::Scalar 如果使用的Perl是古老的版本,会出现不支持标量引用文件句柄的情况,这时候可以用该模块来支持此功能
- IO::Pipe
该模块是
IO::Handle
模块的前端,只要提供一条命令,就自动处理fork
和exec
命令,有点类似于C语言的popen
- IO::Null
创建一个空文件句柄,等效于
/dev/null
- IO::Interactive 返回一个文件句柄,当对该句柄进行写操作的时候,如果调用的程序是daemon则不输出,反之则输出到屏幕
- IO::Dir
- 自 v5.6起,该模块称为Perl标准发行版的一部分,其将对目录的操作打成包,便于使用。
第x章 问题汇总
- name = shift; print "
- 前言
- 第1章 简介
- 1.1 获取帮助的方式
- 1.2 strict和warnings
- 1.3 程序版本
- 1.4 书单
- 第2章 使用模块
- 2.1 CPAN
- 2.2. 阅读模块手册
- 2.3 功能接口
- 2.4 面向对象的接口
- 2.5 核心模块内容
- 2.6 通过CPAN安装模块
- 2.7 搜索路径
- 2.8 在程序外部设置搜索路径
- 2.9 local::lib
- 第3章 中级基础
- 3.1 使用grep过滤列表
- 3.2 使用map转换列表
- 3.3 使用eval捕获错误
- 3.4 使用eval动态编译代码
- 3.5 使用do语句块
- 3.6 require
- 第4章 引用简介
- 4.1 在多个数组上完成相同的任务
- 4.2 Perl图形结构(PeGS)
- 4.3 数组引用
- 4.4 嵌套的数据结构
- 4.5 用箭头简化嵌套元素的引用
- 4.6 散列的引用
- 4.7 数组与散列的嵌套引用
- 4.8 检查引用类型
- 第5章 引用和作用域
- 5.1 循环引用造成内存泄露
- 5.2 匿名数组和散列
- 5.3 自动带入
- 第6章 操作复杂的数据结构
- 6.1 使用调试器
- 6.2 使用 Data::Dumper 模块查看复杂数据
- 6.4 数据编组
- 第7章 对子例程的引用
- 7.1 引用子例程
- 7.2 闭包
- 第8章 文件句柄引用
- 8.1 typeglob
- 8.2 标量
- 8.3 指向字符串
- 8.4 IO::Handle
- 8.5 IO::File
- 8.6 IO::Tee
- 8.7 IO::Pipe
- 8.8 IO::Null
- 8.9 IO::Dir
- 9 正则表达式引用
- 第10章 使用的引用技巧
- 10.1 施瓦茨变换
- 10.2 递归定义的数据
- 10.3 避免递归
- 第11章 构建更大型的程序
- 11.1 基本概念
- 11.2 嵌入代码
- 11.3 命名空间
- 第12章 创建你自己的发行版本
- 12.1 构建工具
- 12.2 Build.PL
- 12.3 Makefile.PL
- 12.3 添加额外的模块
- 12.4 目录文件介绍
- 12.5 文档
- 12.5.1 段落
- 12.5.2 文本格式
- 第13章 对象简介
- 13.1 调用方法
- 13.2 继承
- 13.3 调用父类方法
- 第14章 测试简介
- 第15章 带数据的对象
- 第x章 环境变量汇总
- 第x章 模块汇总
- 第x章 问题汇总
≡ var sidebarTOCBtn = document.getElementById('sidebar-toc-btn') sidebarTOCBtn.addEventListener('click', function(event) { event.stopPropagation() if (document.body.hasAttribute('html-show-sidebar-toc')) { document.body.removeAttribute('html-show-sidebar-toc') } else { document.body.setAttribute('html-show-sidebar-toc', true) } })