前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《Perl进阶》——读书笔记(更新至14章)

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

作者头像
Homqyy
发布2023-03-06 13:28:58
4.6K0
发布2023-03-06 13:28:58
举报
文章被收录于专栏:知行合一知行合一

目录

▶︎

all

running…

前言

  • Perl版本:v5.14

第1章 简介

1.1 获取帮助的方式

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

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 result\n"; }

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 found\n"; }, # 回调函数,每搜索到一个结果都会被调用 @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 = "data1\ndata2\ndata3\n"; 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 regex\n" if string =~ regex; print "smartmatch\n" 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 directory\n";
    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章 问题汇总

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) } })

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021年11月10日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
    • 前言
      • 第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 文档
        • 第13章 对象简介
          • 13.1 调用方法
          • 13.2 继承
          • 13.3 调用父类方法
        • 第14章 测试简介
          • 第15章 带数据的对象
            • 第x章 环境变量汇总
              • 第x章 模块汇总
                • 第x章 问题汇总
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档