《Perl进阶》——读书笔记(更新至14章)

2023-03-06 13:28:58 浏览数 (1)

目录

▶︎

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

  • 所有代码都应该打开strictwarnings,以规范编写的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客户端
  • 手动安装:
    • Makefile.PL:
      1. 下载perl模块包:% wget <URL> (该URL可以从CPAN站点中获取)
      2. 解压perl模块包:% tar -xzf <MODULE.tar.gz>
      3. 进入模块目录:% cd <MODULE>
      4. % perl Makefile.PL
        • 可以用INSTALL_BASE参数来指定安装的路径:perl Makefile.PL INSTALL_BASE=/home/homqyy/
      5. % make
      6. % make test
      7. % make install
    • Build.PL
      1. 下载perl模块包:% wget <URL> (该URL可以从CPAN站点中获取)
      2. 解压perl模块包:% tar -xzf <MODULE.tar.gz>
      3. 进入模块目录:% cd <MODULE>
      4. % perl Build.PL
        • 可以用--install_base参数来指定安装路径:% perl Build.PL --install_base /home/homqyy/
      5. % perl Build
      6. % perl Build test
      7. % perl Build install

2.7 搜索路径

  • perl是通过@INC数组里的路径去搜索模块的,可以通过以下两种方式获取@INC的值:
    1. % perl -V
    2. % 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 语句块后的分号是必须的,因为它是一个术语,语句块是真实的语句块,而不是像ifwhile
  • 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;
    1. do 语句查找文件并读取该文件,然后切换内容为 eval 语句块的字符串形式,以执行它。
    2. 因此 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', };
  • 由于匿名散列与代码块有冲突,因此我们可以在左括号前加入一个 来显示的告诉Perl这是一个匿名散列,在左括号后面加入一个;来显示表示是一个代码块: { '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代码:
代码语言:javascript复制
use Data::Dumper;

my %total_bytes;
while (<>)
{
    my ($source, $destination, $bytes) = split;
    $total_bytes{$source}{$destination}  = $bytes;
}

print Dumper(%total_bytes)
  • 输入文件为:
代码语言:javascript复制
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
  • 结果如下所示:
代码语言:javascript复制
$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:内存
代码语言:javascript复制
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:文件
代码语言:javascript复制
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/flagss/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
  • 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的动态语言无法自动将“尾递归”转为循环,因为再一次调用子例程之前,子例程定义可能改变。
代码语言:javascript复制
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构建

12.1 构建工具

  • h2xs
  • Module::Starter
    • 创建模板:Module::Starter::Plugin
  • Dist::Zilla:这个模块不但可以自动创建发行版,而且在我们修改发行版中的文件后,它还知道如何更新发行包。

12.2 Build.PL

  1. 创建构建框架:% module-starter --module=Animal --author="yourName" --email="yourEmail" --verbose --mb
  2. 创建构建脚本: % perl Build.PL
  3. 开始构建:% ./Build
  4. 执行测试:% ./Build test
  5. 发行前检测一下内容是否有遗漏:% ./Build disttest
  6. 发行版本:% ./Build dist

12.3 Makefile.PL

  1. 创建构建框架:% module-starter --module=Animal --author="homqyy" --email="youEmail" --verbose --builder="ExtUtils::Makemaker"
  2. 创建构建脚本: % perl Makefile.PL
  3. 开始构建:% make
  4. 执行测试:% make test
  5. 发行前检测一下内容是否有遗漏:% make disttest
  6. 发行版本:% make dist

12.3 添加额外的模块

  1. 安装插件:Module::Starter::AddModule
  2. 添加额外的模块: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::PosTest::Pos::Coverage时,./Build test会对Pod进行测试。
  • 测量测试覆盖率:
    1. 安装模块Devel::Cover
    2. 执行% ./Build testcover来进行覆盖率测量
    3. 输出报告:% cover

第15章 带数据的对象


第x章 环境变量汇总

  • PERL5LIB:设置搜索路径
    • Linux可用 : 分隔多个搜索路径
    • Windows可用 ; 分隔多个搜索路径

第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模块
  • IO::File 该模块是IO::Handle模块用于操作文件的子集。属于标准发型版本。
  • IO::Scalar 如果使用的Perl是古老的版本,会出现不支持标量引用文件句柄的情况,这时候可以用该模块来支持此功能
  • IO::Pipe 该模块是IO::Handle模块的前端,只要提供一条命令,就自动处理forkexec命令,有点类似于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) } })

0 人点赞