Perl 基础笔记
Table of Contents
1 开发环境
- 编辑器:Emacs+cperl-mode。设置 cperl-mode 样式:M-x customize-group RET cperl-faces RET。
- Perlbrew:管理本机多个版本的 Perl,可以在不同版本间切换。官网:https://perlbrew.pl/
1.1 帮助文档
perldoc 的使用:
-v 参数可以查看内置变量的帮助文档,往往这些难记的变量不好用搜索引擎检索,如:
$ perldoc -v '$\'
-f 参数查看函数的帮助文档,如:
$ perldoc -f print
-m 参数查看模块的源码:
$ perldoc -m File::Basename
其他参考资料:
- Perl全球用户组:http://www.pm.org/
- CPAN:包含库、文档、示例代码等等。网址:http://www.cpan.org/,注:search.cpan.org 于2018年6月25日下线。
- MetaCPAN:https://metacpan.org
- AnnoCPAN:允许用户注释 CPAN 文档,对文档本身描述不清楚、有坑的地方进行补充。网址:http://www.annocpan.org/
2 标量
数字和字符串都是 Perl 的标量。
2.1 数字
Perl 的数字表示:1、1.0、-1、0、421_559(下划线为了便于阅读)。
进制表示:
print 0xfff, "\n"; # 十六进制 print 0777, "\n"; # 八进制 print 0b1111; # 二进制 print 0xffff_ffff; # 十进制外的数字同样也可以用下划线分割出距离
2.2 数字运算操作
Perl 支持基本的四则运算:+、-、*、/,同时还支持取模(%)、幂(**)运算。
不同进制之间也可进行运算:
print 0xff + 1; # => 256
Perl 也支持自加和自减运算:
$i++; $i--;
2.3 字符串
字符串用单引号或双引号包围,单引号中字符只表示自己,“\n”等控制符也一样,例如“\n”表示反斜杠和字母 n,除了反斜杠需要转义。双引号除了可以用换行等控制字符,还可以在字符串中引用变量,如:
$msg = "hello world"; print "$msg";
Perl 不限制字符串长度,最长可以耗尽内存,最短则叫作空字符串。对于长字符串,Perl 支持 heredoc:
my $html = <<HERE; <html> <head> <title>test</title> </head> <body> </body> </html> HERE print $html;
2.3.1 Unicode
如果要在 Perl 中使用 Unicode,需要在顶部引用 utf8 编译指令,因为 Perl 默认用的 octets 序列,无法自动判断。通常建议作为习惯默认加上 utf8 编译指令:
use utf8;
如果不加 utf8 指令:
print length "我是你爸爸"; # => 15
该字符串长度为 15,Perl 中一个汉字长度为3个字;如果加上 utf8 指令,计算结果便符合预期:
use utf8; print length "我是你爸爸"; # => 5
2.3.2 字符串操作
连接两个字符串用“.”:
print 'hello' . ' ' . 'world'; # => hello world # 如果配合数字使用,结果为字符串 print 123 . 'abc'; # => 123abc
重复字符串(字符串后跟小写字母 x):
print 'A' x 3; # => AAA print 1 x 3; # => 111,等同重复三次字符串“1”
分割字符串:
my $str = "hello world"; my @words = split / /, $str; print @words[0]; # => hello print @words[1]; # => world
字母大小写转换:
# uc 函数提供转换成大写字母 print uc "hi"; # => HI # lc 函数提供转换成小写字母 print lc "HI"; # => hi
2.3.3 字符和数字的转换
是字符串还是数字,取决于操作符,遇上四则预算则为数字运算:
'4' * '2'; # => 8 '4' + '1'; # => 5 '4' - '1'; # => 3 '4' / '2'; # => 2 '123abc' * 2; # => 246,非数字部分的”abc“会被忽略 'abc' * 2; # => 0,没有数字就取值为 0
2.4 undef 和 defined
代码中引用未定义的变量,其值为 undef。当然也能直接创建值为 undef 的变量:
$a = undef;
当数字遇到 undef 时,赋值为0:
$i += 10; print $i; # => 10
字符串遇到 undef 时,赋值为空字符串:
$msg .= "hello"; print $msg; # => hello
可以用 defined 函数来判断值是否为 undef,比如直接读取到文件尾(EOF)时就返回 undef。
例,检查脚本参数:
if (not defined $ARGV[0]) { exit; }
3 常量和变量
Perl 变量以“$”开头,区分大小写,只能包含下划线、数字和字母,且不能以数字开头。如果启用了 utf8 指令,也可以用 Unicode 字符做变量名。
$msg = "hello world";
3.1 双目运算
$a = 1; $b = 2; $c = 3; $d = 4; $e = 'hello'; $a += 1; # 等同 $a = $a + 1 $b -= 2; # 等同 $b = $b - 2 $c *= 3; # 等同 $c = $c * 3 $d /= 4; # 等同 $d = $d / 4 $e .= ' world'; # 等同 $e = $e + ' world'
3.2 字符串中引用变量
$hello = 'hello'; $world = 'world'; print "$hello $world\n"; print "${hello} ${world}";
3.3 常量
use constant PI => 3.1415926;
print PI;
4 POD
单独维护代码和文档是非常痛苦的,为了便于把文档直接写到代码文件中,Perl 干脆内置了 POD(Plain Old Documentation)标记语言,直接在源代码中写文档,然后用 pod2html 等工具来生成文档。这可不像其他语言依赖写特定格式的函数注释。
详细见:perldoc perlpod。
可在 CPAN 上找一些三方库参考别人如何写的,如:http://cpansearch.perl.org/src/ALEXP/Net-Domain-TLD-1.75/lib/Net/Domain/TLD.pm
5 流程控制
5.1 逻辑判断
Perl 没有布尔类型,标量仅在以下情况下为”假“,其余情况为”真“:
- 数字0;
- 当值为字符串时,空字符串和'0'为假;
- undef;
- 空 list。
Perl 的逻辑判断和大多脚本语言的不同,字符串和数字有两套语法:
比较 | 数字比较 | 字符串比较 |
---|---|---|
相等 | == | eq |
不等 | != | ne |
小于 | < | lt |
大于 | > | gt |
小于等于 | <= | le |
大于等于 | >= | ge |
并且数字和字符串之间的比较是不相同的,比如:
'1' == '1'
表示的是数字比较,而不是字符串比较,字符串比较应该用 eq。
Perl 的逻辑操作符:
&&:逻辑AND ||:逻辑OR !:逻辑NOT
5.2 if,elsif,else
语法:
if (...) { ... } # 或: if (...) { ... } else { ... }
如果要多个判断,可以可以用 elsif,语法如下:
if (...) { ... } elsif (...) { ... } elsif (...) { ... } else { ... }
注意大括号是不可省去。
5.3 unless
和 if 相反,表达式不为真时才会执行块中的代码。
5.4 ?:
和 C 中的一样,语法格式:
表达式 ? 如果为真 : 如果为假
5.5 given-when
类似 C 语言的 switch,但比 switch 更高级:
use 5.010001; given($ARGV[0]) { when ('-h') {print 'help'} when ('-a') {print '-a'} default {print 'default'} }
5.6 do
do 语句能让多个表达式组合成一个表达式,如简化 if..elsif 判断赋值。如下代码,没有 do 的情况:
my $code_type; if ($code == 'LEFTCTRL') { $code_type = 'left'; } elsif ($code == 'ENTER') { $code_type = 'enter'; } elsif ($code == 'BACKSPACE') { $code_type = 'backspace'; }
现在改成 do 版:
my $code_type = do { if ($code == 'LEFTCTRL') {'left'} elsif ($code == 'ENTER') {'enter'} elsif ($code == 'BACKSPACE') {'backspace'} }
6 循环
6.1 while
while (...) {
...
}
6.2 foreach
示例1:
foreach $i (1..10) { print "$i\n"; }
示例2,遍历 shell 命令调用结果:
foreach $i (`ls /etc`) { print "$i"; }
foreach 的控制变量(如上面两个例子中的变量 i),会在 foreach 结束后恢复变量的值,如果值不存在就是 undef。如:
$i = 100; foreach $i (1..10) { $i .= "\n"; } print $i; # 输出:100
foreach 还可以省略控制变量,改用”$_“代替:
foreach (1..10) { print "$_\n"; }
6.3 for
和 C 的一样,语法:
for (初始化; 测试条件; 递增) {
...
}
如:
for ($i = 0; $i < 100; $i++) { print $i; }
也可以写出死循环:
for (;;) {
...
}
Perl 会根据括号中的表达来决定代码执行意图,如果括号里没有分号,就说明是 foreach 循环,如:
for (1..10) {
print;
}
6.4 until
while 的相反语句,只有表达式不为真时才执行块中的代码。
6.5 循环控制
6.5.1 last
类似其他语言的 break。last 直接中断循环,如:
# 打印标准输入的内容 while(<STDIN>) { # 一旦遇到__END__,说明输入结束 if (/__END__/) { last; } print; }
6.5.2 next
类似其他语言的 continue,循环中遇到 next,就跳过当前循环。如,打印 100 以内的偶数:
for (1..100) { next if ($_ % 2 == 1); print "$_\n"; }
6.5.3 redo
重复某次循环,如:
$i = 0; foreach ("Perl", "Python", "Ruby", "Scheme") { $i++; redo if $i == 3; print "$i, $_\n"; }
运行后输出如下:
1, Perl 2, Python 4, Ruby 5, Scheme
6.5.4 带标签的块控制
如下结构:
while (...) { foreach (...) { ... } }
想在 foreach 里中止上层的 while 循环怎么办?用带标签的块结构即可。标签建议用全大写命名。示例:
LABEL:while (1) { foreach (1..10) { # 直接在 last 后跟标签名即可 last LABEL if ($_ == 5); } }
7 列表和数组
7.1 初始化数组
数组可以不用事先定义就创建:
$books[0] = 'Learning Perl'; $books[1] = 'Intermediate Perl';
定义一个数组:
@books = ('Learning Perl', 'Intermediate Perl'); # ()表示空数组 $books[0];
也可用 qw(…) 语法来省略字符串的引号,Perl 会将元素按空白字符分割:
@books = qw(a b c); $books[0];
qw 的定界符并不固定,也可指定成其他的,如:
qw( a b c ); qw{ a b c }; qw< a b c >; qw/ a b c /; qw# a b c #; qw! a b c !;
如果元素包含定界符相同的符号,需要用反斜杠转义,比如:
qw! a b c \! !;
赋值:
($a, $b, $c) = (1, 2, 3); # 实质执行了 3 次赋值操作 print $a, $b, $c;
明白这种赋值方式,就可以理解赋值给一个变量时为何要加”@“开头,”@“表示批量赋值。
@a = (1, 2, 3); @b = @a; # 复制数组 $a[0] = 10; # 不会改变数组 a 的内容 print $a[0], ' ', $b[0];
7.2 数组长度
my @languages = qw(Perl Ruby Lisp Python Go); print scalar @languages; # 方法1:用 scalar my $count = @languages; # 方法2:直接赋值 print $count; print $#languages + 1; # 方法3:最后一个元素的下标加 1 就是数组大小
7.3 数组索引
数组表示变量,列表才表示具体数字。Perl 不限制列表大小,直至内存用尽。数组元素可以是任意类型。
$books[1]; $books[100]; # 超出索引范围返回 undef,而不会报异常
$#books可取出最后一个元素的下标:
$books[$#books];
或者用负数做下标:
$books[-1]; # 倒数第一个元素 $books[-2]; # 倒数第二个元素
Perl还支持按范围生成数组:
@a = (1 .. 3); @b = (a .. z);
7.4 数组操作
push
@a = (1 .. 3); push(@a, 4); print $a[-1]; # 输出:4
unshift 和 push 操作方向相反。
@a = (1 .. 3); unshift(@a, 0); print $a[0]; # 输出:0
pop pop 操作在空数组上则返回 undef。pop 示例代码:
@a = (1 .. 3); $last = pop(@a); print $last; # 输出:3 print $a[-1]; # 输出: 2
shift 和 pop 操作方向相反。
@a = (1 .. 3); $item = shift(@a); print $item; # 输出:1 print $a[0]; # 输出:2
splice 返回部分数组元素并从原始数组中删除。
splice 接受四个参数:
- 参数1:数组;
- 参数2:删除的起始位置;
- 参数3:可选参数。删除的长度;
- 参数4:把参数 4 数组内容替换到原来数组被删除的位置。
@a = (1 .. 10); splice(@a, 8, 1); # 删除元素 9 foreach $item (@a) { print $item; } # 输出:1234567810
指定参数 4,把新元素替换到原来数组被删除的位置:
@a = (1 .. 10); splice(@a, 8, 1, qw(a b c)); # 将a、b、c替换到元素 9 的位置上 foreach $item (@a) { print $item; } # 输出:12345678abc10
字符串中插入数组
@list = qw{a b c}; print @list; # 输出:a b c
注意的是,如果要把 E-mail 地址作为邮箱,要么用单引号字符,要么转义”@“。
reverse:逆转数组
@a = (1..5); print reverse @a; # 输出54321
sort:排序数组
@a = (5, 4, 3, 2, 1); print sort @a; # 输出12345
map:对项逐一应用
print map {$_ + 1} (1, 2, 3); # => 234
grep:过滤元素
my @new = grep $_ > 10, (10, 20, 1, 3, 11, 28);
7.5 数组切片
open IN, "< /etc/passwd"; while(<IN>) { my @line = split /:/; print "@line[0, 4]\n"; }
7.6 遍历数组
my @languages = qw(Perl Python Ruby Lisp); for my $lang (@languages) { print $lang, "\n"; }
也可以像 C 语言中那样,按下标遍历:
my @languages = qw(Perl Python Ruby Lisp); for my $i (0 .. @languages) { print $languages[$i], "\n"; }
8 hash 表
示例:
use 5.010; # 定义Hash表以“%”开头 %a_hash = ( 'a' => 1, 'b' => 2 ); say $a_hash{'a'}; # 访问 key $a_hash{'c'} = 3; # 修改键值 say %a_hash; # 访问整个 hash 表
判断 key 是否存在于 hash 表中
if (!exists $a_hash{"c"}) { print "不存在"; }
从 hash 表中删除键值
%a_hash = ( 'a' => 1, 'b' => 2, 'c' => 3 ); delete $a_hash{'c'}; print keys %a_hash;
keys 和 values 函数
keys 函数返回 hash 表所有 key;values 函数返回 hash 表所有的 value:
say keys %a_hash; # => ab say values %a_hash; # => 12
遍历 hash 表
%a_hash = ( 'a' => 1, 'b' => 2 ); while (($k, $v) = each %a_hash) { printf "key: %s, value: %s\n", $k, $v; }
%ENV
%ENV 保存了当前的环境变量,例如输出 PATH 环境变量:
print $ENV{"PATH"};
9 输入输出
print 用于打印消息。print 默认不打印换行符,如需默认打印换行符,就设置 $\ 的值:
$\ = "\n"; print "hello world";
print 打印数组时,如果数组是在字符串中引用,打印时会有空格分离:
@a = qw /a b c/; print @a; # 输出:abc print "@a"; # 输出:a b c
如需格式化输出,请用 printf 函数。
printf 打印数组:
@users = qw(user1 user2 user3); $format = "users are: @users"; printf $format, @users; $format = "users are: " . ("%s " x @items); # 后面是重复 @items 元素个数多少次 printf $format, @users;
Perl5.10 开始增加了一个新函数——say,say 输出时会带上换行符:
use 5.010; # 必须启用 5.10 的特性才可以 say "hello"; say "world";
9.1 输出 Perl 警告信息
要打开 Perl 的警告,可在 perl 命令加上 -w 参数:
$ perl -w hello.pl
或者开启警告指令(Perl 5.6 开始才支持):
use warnings;
如果要警告内容更详细的信息,可开启 diagnostics 指令:
use diagnostics;
更常用的是在 perl 命令使用 -M 参数:
$ perl -Mdiagnostics hello.pl
9.2 标准输入
<STDIN> 用于获取用户的标准输入。
例,逐行打印标准输入的文本:
# foreach 版,Perl 会先把内容放到数组里循环,所以不建议用 foreach $line (<STDIN>) { print $line; } # while 版才是逐行读取,不会预先读取 while (<STDIN>) { print $_; }
9.2.1 chomp
去除换行符:
foreach $line (<STDIN>) { chomp($line); print $line; }
精简版:
foreach (<STDIN>) { chomp; # chomp 默认作用在 $_ 上 print $_; }
9.3 钻石(<>)读取
Perl 为了更适应 Unix 风格,发明了“<>”操作符,当程序指定多个输入源作为参数时,Perl 会逐一读取文件,如果其中一个参数是“-”,Perl 会从标准输入中读取:
while(<>) { print $_; }
运行:
$ echo hi | ./test.pl /etc/passwd /etc/hosts -
这等同把 /etc/passwd、/etc/hosts 和标准输入的内容合并在了一起。
9.3.1 接受脚本参数
参数存在 @ARGV 数组中:
foreach (@ARGV) { print $_, "\n"; }
注意钻石符号就依赖 @ARGV,比如显示设置 @ARGV 的内容,脚本就会自动读取:
@ARGV = qw# /etc/passwd #; foreach (<>) { print $_, "\n"; }
9.4 文件句柄
open 操作符用来打开其他文件句柄。
示例:
open f, "/etc/hosts"; foreach $line (<f>) { print $line; }
关闭文件句柄用 close 操作符:
close f;
9.4.1 读、写和追加模式
Perl 里用“<”、“>”和“>>”分别表示读、取和追加,完整示例如下:
open PASSWD, "</etc/passwd"; # 读取模式 open OUT, ">out"; # 写入模式 open LOG, ">>log"; # 追加模式 while(<PASSWD>) { print $_; } ### 向文件中写或追加数据时,用 print、printf 和 say 函数,指定句柄即可 print OUT "hello world"; # print 指定输出句柄参数时不用加逗号 say LOG "test1"; # say 指定句柄的规则和 print 一样 say LOG "test2"; close PASSWD; close OUT; close LOG;
另一个读取文件所有内容的例子:
open IN, "/etc/passwd"; my $lines = join '', <IN>; print $lines;
9.4.2 处理错误
如果打开句柄失败,open 操作符返回假,这时可以用 die 或 warn 函数来处理错误:
$f = open F, "<not_exists"; if (! $f) { die "打开文件失败"; } close F;
die 和 warn 会打印出错误信息已经行号,不同的是 die 函数输出错误后会结束程序的运行。
9.4.3 改进文件句柄
从 Perl v5.6 开始,可以用普通标量作为 open 的参数,完整示例代码如下:
open my $f, ">", "a"; print ${f} "hello world\n"; close $f;
这也是最推荐的方法,标量还可以作为参数传递。
9.4.4 字符串句柄
open 除了新建一个文件句柄,也可以打开一个字符串句柄,并不断地向字符串句柄写数据:
open my $f, ">", \my $str; print ${f} "hello world\n"; print ${f} "hehe\n"; print ${f} "just test"; close $f; print $str;
9.4.5 临时改变句柄
下面这段代码将临时改变标准输出句柄——将内容输出到一个字符串中:
my $str; { local *STDOUT; open STDOUT, '>', \$str; print "hehe"; close STDOUT; } print $str;
local 关键字和 my 不一样的是,local 只是临时改变标量的指向;my 是创建新的标量。
9.4.6 另一种方式逐行遍历字符串
也可以让字符串作为句柄输入,这样便可逐行遍历:
my $str = "1\n22\n333\n4444\n"; open $f, "<", \$str; while (<$f>) { print; }
9.4.7 文件目录句柄
opendir my $dir, "/etc" or die "open error"; foreach my $file (readdir($dir)) { print "$file\n"; }
9.4.8 将文件全部内容读入到字符串
{ local $/ = undef; open F, "/etc/hosts" or die "open file error"; $content = <F>; close F; } print $content;
10 子程序
Perl 中,函数叫作“子程序”,用 sub 关键字定义,例:
sub hello { print "hello world"; }
调用子程序的三种方式:
hello; # 如果不加括号不影响表达式含义,可省掉括号 hello(); &hello; # 旧风格
如果子程序和内置子程序重名冲突,调用时则加上“&”开头。
子程序的返回值默认是子程序最后表达式执行结果,也可以在子程序中显示使用 return。
10.1 传递参数
Perl 把参数自动存入 @_ 数组中,可直接访问,如:
sub say_msg { print $_[0]; } say_msg 'hello world';
每次调用子程序,都会分配一个私有的 @_ 数组,所以不用担心被覆盖。
判断参数个数:
sub say_msg { if (@_ != 1) { print '参数错误'; return; } print $_[0]; }
10.2 定义私有变量
子程序中定义的变量默认是全局变量,如:
sub test { $n += 1; } &test; # n = 1 &test; # n = 2 &test; # n = 3 print $n; # => 3
使用 my 关键字定义子程序中的私有变量,如下:
sub say_msg { my($msg) = @_; # msg 作用于仅限 say_msg 子程序中 print $msg; } say_msg 'hello world';
my 也可以用在其他代码块中,如 foreach:
foreach (1 .. 10) { my($i) = $_; print $i; }
这里的变量 i 作用域范围仅限 foreach 中。
如果需要先声明私有的、持续的变量(比如在用了 strict 时编译器会提示变量未声明),就用 state 关键字,这是 Perl 5.10 新增的:
use 5.010; sub test { state $n = 0; $n += 1; } test; test; print test; # 输出:3
10.3 原型匹配
sub test($;) { print @_; } test; # 报错:Not enough arguments for main::test test "hi";
这要求调用者必须提供参数。
11 更多 Perl 结构
11.1 表达式修饰
为了减少输入,Perl 支持一些简化的表达式:
# 逻辑判断 print "hello" if 1 > 2; # 遍历数组 print($_) foreach (1, 2, 3); # 循环 $i = 0; print ($i += 1) while $i < 10; $i = 0; print ($i += 1) until $i > 10;
11.2 裸块
裸块就是单独的一对花括号之间的内容,它可以有自己的作用域:
{ my $i = 100; print $i; # $i 的作用域仅限于裸块之内 }
12 正则表达式
在 Perl 中正则匹配也叫“模式匹配”,其结果就是两种:匹配或不匹配。最简单的模式匹配就是用“/”包围:
open IN, "< /etc/passwd"; # “/../”会去从 $_ 搜索匹配 while(<IN>) { if (/bash/) { print $_; } }
12.1 unicode 属性
完整的属性集请参加:https://perldoc.perl.org/perluniprops.html。
举例,判断字符是否是 Cyrillic 字符集:
use utf8; # 前面说过,必须要启用 utf8,Perl 才会把字符串作为 unicode $_ = 'ӏ'; if (/\p{Block: Cyrillic}/) { print "yes"; }
12.2 正则修饰符
/i:忽略大小写
$_ = "hello World"; print /world/i;
/x:允许在正则中插入空白
$_ = "192.168.1.1"; print /\d{1,3} \. \d{1,3} \. \d{1,3} \. \d{1,3}/x;
/x模式下同时也允许注释存在:
$_ = "192.168.1.1"; print / \d{1,3} \. # A 段 \d{1,3} \. # B 段 \d{1,3} \. # C 段 \d{1,3} # D 段 /x;
\A和\z
\A指定开头字符,只有匹配对象是指定的字符开头,才会继续匹配下去;\z指定结尾字符。
12.3 =~
让右边的模式去匹配左边的字符串(默认是用$_去匹配):
print "192.168.1.1" =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
匹配标准输入:
if (<STDIN> =~ /root/) { print "yes"; }
12.4 捕获匹配
如果匹配成功,结果按分组保存在 $1、$2… 变量中:
open IN, "/etc/passwd"; while (<IN>) { if (/\/(\w{1,2}sh)/) { # 注意这里 $1 的引用,一定是在判断捕获成功后才能引用 # 否则它的值可能是上一个表达式捕获的 print "$1\n"; } }
12.4.1 命名捕获
格式为:(<?name>pattern),例:
open IN, "/etc/passwd"; while (<IN>) { if (/\/(?<shell>\w{1,2}sh)/) { print "$+{shell}\n"; } }
12.4.2 捕获变量
$&:保存实际匹配到的部分
$`:保存匹配到的字符串之前的内容
$':保存匹配到的字符串之后的内容
12.4.3 将所有匹配到的内容存入数组
my $str = "1a2b3c4d"; my @numbers = $str =~ /\d/g; print @numbers; # => 1234
12.5 文本处理
12.5.1 s///:文本替换
open IN, "/etc/passwd"; while (<IN>) { if (s/root/ROOT/g) { # /g 表示全局替换,否则只替换一次 print $_; } }
13 智能匹配:~~
当操作对象是一个 hash 与正则时,智能匹配就遍历 hash表,判断 key 是否匹配正则:
%a = ('Perl' => 1, 'Python' => 2); print %a ~~ /Py/;
如果两边都是数组,则是比较数组是否相等:
@a = (1, 2, 3); @b = (1, 2, 3); print @a ~~ @b;
更多请见 perlsyn 文档:https://perldoc.perl.org/perlsyn.html。
14 异常捕获
Perl 中可用 eval 作为异常捕获,将有可能会发生异常的代码块放在 eval 中执行,如果执行顺利,eval 就返回代码块最后一条的执行结果,否则返回 undef。在 eval 中发生严重错误都不会导致整个程序崩溃:
my $result = eval { 1/0; # 这里不会导致 Perl 崩溃 }; print $result;
具体的错误信息保存在 [email protected] 变量中的:
{ local [email protected]; # 不影响其他层次的错误消息捕获 my $result = eval { 1/0; }; print [email protected]; }
eval 还有另外一种用法,如果传递的是字符串,则执行字符串表达的代码。当说 eval 不安全的时候,请区别对待。
15 文件测试
Perl 中有丰富的文件测试符,如用 -e 测试文件是否存在:
die "file not exists" if ! -e '/etc/passwd';
要查看完整的清单,可用命令:perldoc -f -X,注意 -X 并非 perldoc 的参数。
16 引用
引用是指让某个变量指向某个数据结构。
my @list = (1, 2, 3); my $p = \@list; # $p 指向列表地址
$p 指保存了 @list 的内存地址,如果要取 @list 的元素,需要做解引用操作,语法:
@{$p}; # 指向整个数组 ${$p}[0]; # 指向具体的某个元素,当然用 @{$p}[0] 也可以访问具体元素 # 也可以去掉大括号: @$p; $$p;
要使用循环遍历引用对象,直接解引整个数组即可:
foreach $i (@{$p}) { print $i; }
对于 hash 结构,使用方式也相同,唯一区别是解引用时把”@“替换成”%“了:
my %hash = ('a' => 1, 'b' => 2); my $p = \%hash; print %{$p}{'a'}; print ${$p}{'a'}; while (($k, $v) = each %{$p}) { print "$k, $v\n"; }
对于解引用,还可以用箭头来简化语法,使代码变得更整洁:
my @list = (1, 2, 3); my $p = \@list; print $p->[1];
16.1 检查引用类型
ref 函数可以用于检查引用类型:
my @list = (1, 2, 3); my %hash = ('a' => 1, 'b' => 2); my $p1 = \@list; my $p2 = \%hash; print ref $p1; # => ARRAY print ref $p2; # => HASH
16.2 匿名数组引用
匿名数组,是保存了指向某个数组的内存地址,如:
my $ref; { @list = (1, 2, 3, 'a', 'b', 'c'); $ref = \@list; } print $ref;
虽然 $ref 在不同的作用域中进行了赋值——指向 @list 的内存地址,但由于 Perl 垃圾回收用了引用计数法,指向 @list 的内存地址后,该数组的引用计数加 1,所以即便离开了作用域,$ref 指向的结构仍旧有效,这时 $ref 指向的就叫匿名数组——因为这个数组没有和任何变量名绑定在一起。
用法1:
my $ref = [(1, 2, 3)]; print $ref->[0];
用法2:
my $ref = [1, 2, 3]; print $ref->[0];
16.3 匿名hash表
和匿名数组相似。创建方法:
my $ref = { 'a' => 1, 'b' => 2, 'c' => 3 }; print $ref;
使用匿名 hash 有一点要注意,因为语法用的大括号,而代码块也用的大括号,有时需要显示区分:
告诉编译器此处为匿名 hash:+{ ... } 告诉编译器此处为代码块:{; ... }
16.4 子程序引用
也可以让变量指向子程序的内存位置,对子程序进行引用:
sub say { my $msg = shift; print "$msg\n"; } my $sub_ref = \&say; &{$sub_ref}("hi");
对 $sub_ref 解引用操作还有更优美的方式:
$sub_ref->("hi");
既然子程序可以引用,那么就可以创建匿名子程序:
my $say = sub { my $msg = shift; print "$msg\n"; }; $say->("hi");
17 模块
corelist 命令可以帮助查看系统中已有的 Perl 模块,如:
$ corelist /File::/
查看系统中全部模块:
$ corelist //
要查看模块的文档,就可借助 perldoc 了。如下,查看 :Basename 的帮助文档:
$ perldoc File::Basename
17.1 模块引入
17.1.1 use
内置的 use 函数可引入其他模块,例:
use File::Basename; print basename("/etc/passwd");
use 函数导入默认的子程序,也可指定只导入哪些子程序:
use File::Basename qw(basename); print basename("/etc/passwd");
如果第二个参数是一个空列表,那表示什么子程序都没有引入到当前作用域中,就需要指定模块访问:
use File::Basename (); print File::Basename::basename("/etc/passwd");
17.1.2 do
do 语句的另一个用法,当传递给 do 语句的参数是字符串时,Perl 将字符串当作是其他 Perl 文件来导入,如:
do "Hello.pm";
17.1.3 require
对于某模块,一旦已经被 require,再重复 require 将会被 Perl 忽略。
require 还有两个特性:
1、导入的文件出现任何语法错误,Perl 都会被中止;
2、导入的文件的最后一个表达式必须返回真值,所以很多文件最后一行代码是一个“1”。
17.2 命名空间
在没有命名空间的情况下,导入的模块中的子程序可能会和当前空间的子程序冲突,Perl 用 package 来定义命名空间:
package Hello; sub say { my $msg = shift; print "$msg\n"; } 1
其他模块 require 后,用“包名::子程序”的语法格式来引用:
Hello::say("hello world");
17.2.1 package 作用域
package 作用域是在 package 指令之后,带代码块的作用域中:
{ package Hello; sub say { print "in `Hello` package\n"; } } sub say { print "in main\n"; } say; # 输出:in main Hello::say # 输出:package Hello
从 Perl 5.12 开始,就可以在 package 语句后直接跟代码块了。上面代码改写后:
package Hello { sub say { print "in `Hello` package\n"; } } sub say { print "in main\n"; } say; # 输出:in main Hello::say # 输出:package Hello;
18 CPAN
CPAN 包含了丰富的 Perl 库。
搜索模块:
cpan[1]> m /HTTP/
安装模块:
cpan 模块名
如:
$ cpan HTTP::Server::Simple
或在交互式下:
cpan[1]> install HTTP::Server::Simple
Perl 加载库时会依次在 @INC 变量中设定的路径开始查找,可以用 perl -V
看到 @INC 的内容。非 root 身份下用 cpan 安装的库默认装在当前用户的 home 目录中的,因此要在 .bashrc 中设置路径才能正常工作:
export PERL5LIB=~/perl5/lib/perl5:$PERL5LIB