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

其他参考资料:

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}";

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;

具体的错误信息保存在$@变量中的:

{
    local $@; # 不影响其他层次的错误消息捕获

    my $result = eval {
	1/0;
    };

    print $@;
}

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