综述
官网
Perl语言官网:<The Perl Programming Language - www.perl.org>。
简述
Perl语言的应用范围很广,除CGI以外,Perl被用于图形编程、系统管理、网络编程、金融、生物以及其他领域。由于其灵活性,Perl被称为脚本语言中的瑞士军刀。
Perl语言的优点:
- 相比C、Pascal这样的“高级”语言而言,Perl语言直接提供泛型变量、动态数组、Hash表等更加便捷的编程元素;
- Perl具有动态语言的强大灵活的特性,并且还从C/C++、Basic、Pascal等语言中分别借鉴了语法规则,从而提供了许多冗余语法;
- 在统一变量类型和掩盖运算细节方面,Perl做得比其他高级语言(如:Python)更为出色;
- 由于从其他语言大量借鉴了语法,使得从其它编程语言转到Perl语言的程序员可以迅速上手写程序并完成任务,这使得Perl语言是一门容易用的语言;
- Perl语言是可扩展的,我们可以通过CPAN(Comprehensive Perl Archive Network)中心仓库找到很多我们需要的模块;
- Perl语言的
mod_perl
模块允许Apache Web服务器使用Perl解释器。
Perl语言的缺点:
- Perl语言的灵活性和“过度”的冗余语法,使得许多Perl程序的代码令人难以阅读和维护(变成了“Write-Only”);
- Perl语言随意的特点,可能会导致一些Perl程序员遗忘语法,以至于不得不经常查看Perl手册;
建议的解决方法是在程序里使用use strict;
以及use warnings;
,并统一代码风格,使用库,而不是自己使用“硬编码”。
Perl擅长处理整体来说“约有90%与文字处理有关,10%与其它事务有关”的问题。
Perl是编写小型CGI的最佳语言。
Perl的注释用#
,没有块注释。
Perl程序并不需要变量声明的部分。
源代码使用Unicode书写的话,需要使用use utf8;
,除非有明确的原因不加,否则就应该用上。
下载和安装
下载Perl编译环境,需要注意的是Windows版本下有两种编译环境:
就目前的使用来看没法确定差别,暂时下载Strawberry Perl,因为它不需要账户就可以下载。下载之后安装,安装之后可以通过如下的方式查看:
表示已经安装成功。
HelloWorld
代码位于Basic\HelloWorld.pl:
#!/usr/bin/perl
use utf8;
print "Hello, World!\n";
执行的结果:
E:\Gitee\perl\Code\Basic>HelloWorld.pl
Hello, World!
E:\Gitee\perl\Code\Basic>
参考书
《Learning Perl》(《Perl语言入门》)
《Internediate Perl》(没中文)
《Mastering Perl》(《精通Perl》)
《Programming Perl》(《Perl语言编程》)
其它
一般P大写的Perl表示程序语言,P小写的perl表示实际编译并运行的解释器。
基本语法
标量数据
标量是Perl里面最简单的一种数据类型,分为数字和字符串(其实还有undef
)。
- 数字分为整型和浮点型,但是在Perl内部都是浮点型,整型也是由浮点型表示的。
- 字符串可以用单引号和双引号,双引号中的反斜杠更为强大,可以转义许多控制字符:
- 单引号中的
\n
是两个字符; - 双引号中的
\n
是换行符; - 诸如此类的差异。
- 单引号中的
数字和字符串会自动转换,如何转换主要依赖于操作符。
字符串操作
连接操作用.
:
#!/usr/bin/perl
use utf8;
use warnings;
print "hello" . " " . "world"; # 点号用来连接
字符串重复使用x
,其实就是小写的x字符:
#!/usr/bin/perl
use utf8;
use warnings;
print "hello" x 3;
注意下面的例子:
#!/usr/bin/perl
use utf8;
use warnings;
print 5 x 4.8 # 打印结果5555
因为使用了x
所以是字符串重复操作,而该操作左侧必须是字符串,所以5被转换成了"5",右侧的数字又会被取整,所以变成了4,最终就输出了"5555"。当重复次数小于1时会返回空字符串:
#!/usr/bin/perl
use utf8;
use warnings;
print "hello" x 0.1; # 打印为空
标量变量
标量变量是单单存储一个值的变量。
标量变量名以美元符号开头。
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "Hello"; # 定义变量
$space = " "; # 定义变量
$world = "world"; # 定义变量
print $hello, $space, $world; # print后面的参数可以用逗号隔开,实际上是个列表
变量可以内插,即把字符串内出现的所有标量变量替换成变量当前的值:
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "hello";
print "$hello world"; # 变量内插
如果变量没有被赋值,就会用空字符串来替换。
如果只要打印变量值,则不需要使用变量内插:
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "hello";
print $hello; # 变量内插,这种情况可以不需要要双引号,不过也可以加
变量内插只适用于双引号:
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "hello";
print "$hello"; # 打印hello
print '$hello'; # 打印$hello,多一个$
内插的变量会以最长且合法的方式获取:
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "hello";
$helloworld = "hello world";
print "$helloworld";
如果想使用$hello
,可以用大括号{}
括起来:
#!/usr/bin/perl
use utf8;
use warnings;
$hello = "hello";
$helloworld = "hello world";
print "${hello}world"; # 使用{}
代码点创建字符
chr()
函数可以将数字转换成字符:
#!/usr/bin/perl
use utf8;
use warnings;
print chr(0x30); # 0的ASCII是0x30,所以这里打印0
print chr(0x31); # 1的ASCII是0x30,所以这里打印1
ord()
执行相反的操作:
#!/usr/bin/perl
use utf8;
use warnings;
print ord('0'); # 打印48
布尔值
- 如果是数字,0为假,其它为真;
- 如果是字符串,空字符串(
""
或''
)为假,其它为真(有例外,见下面); - 如果既不是数字也不是字符串,那就先转换成数字或者字符串再行判断;
- 字符串’0’和数字0是同一个标量值,所以都为假:
#!/usr/bin/perl
use utf8;
use warnings;
if (0) {
print "0 is true\n";
} else {
print "0 is false\n"; # 执行到这里
}
if ('0') {
print "'0' is true\n";
} else {
print "'0' is false\n"; # 执行到这里
}
if ('') {
print "'' is true\n";
} else {
print "'' is false\n"; # 执行到这里
}
undef
也是假
#!/usr/bin/perl
use utf8;
use warnings;
if (undef) {
print "undef is true\n";
} else {
print "undef is false\n"; # 执行到这里
}
如果有else if,则使用elsif
,注意少一个e
。
前面的布尔值可用于if
,还可以用于while
:
#!/usr/bin/perl
use utf8;
use warnings;
$count = 2;
while ($count) {
$count -= 1;
print "count: $count\n";
}
获取用户输入
“行输入”操作符是<STDIN>
:
#!/usr/bin/perl
use utf8;
use warnings;
$input = <STDIN>;
print "input: ", $input;
<STDIN>
默认(通过$/
存放换行符)在遇到换行符之后退出。
<STDIN>
返回的字符中包含换行符。
chomp()
函数作用于字符串,用于去掉字符串末尾的换行符(注意只去掉行尾)并返回:
#!/usr/bin/perl
use utf8;
use warnings;
$input = <STDIN>;
chomp $input;
print "input: ", $input;
注意chomp()
是有返回值的,所以下面的代码跟上面的代码不同:
#!/usr/bin/perl
use utf8;
use warnings;
$input = <STDIN>;
print "input: ", chomp $input; # 打印input: 1
input: 1
表示chomp()
去掉的字符数,这里就是1个换行符。
$/
记录了分隔符,它默认就是换行符\n
, 设置这个操作符会影响chomp()
,chomp()
默认是去掉行尾的\n
,当设置了$/
,chomp()
会去掉$/
设置的符号。
local $/ = "END";
上述的语句之后chomp()
会将END
认为换行符:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
local $/ = "END";
while (<>) {
chomp;
print "$_\n";
}
执行上述的代码结果:
E:\Gitee\perl\Code\Basic>Input.pl
nihao
END
nihao
只有当输入END
才会往下执行。
undef
在首次赋值前,变量的初始值就是特殊的undef
。
- 当作为数字时,它表现得像0;
- 当作为字符串时,它表现得像空字符串。
- 但
undef
即不是数字也不是字符串,它是另一种类型的标量值。
defined()
函数用于判断某个变量是undef
还是空字符串,如果是undef
则返回假:
#!/usr/bin/perl
use utf8;
use warnings;
$var;
if (defined($var)) {
print "defined\n";
} else {
print "undef\n" # 执行到这里
}
$var = '';
if (defined($var)) {
print "defined\n"; # 执行到这里
} else {
print "undef\n"
}
运算符
比较操作符
比较 | 数字 | 字符串 |
---|---|---|
相等 | == | eq |
不等 | != | ne |
小于 | < | lt |
大于 | > | gt |
小于或等于 | <= | le |
大于或等于 | >= | ge |
逻辑运算符
表格示例中我们设置变量$a
为true,$b
为false。
运算符 | 描述 |
---|---|
and | 逻辑与运算符符。如果两个操作数都为true,则条件为true。 |
&& | c语言风格的逻辑与运算符符。如果两个操作数都为true,则条件为true。 |
or | 逻辑或运算符。如果两个操作数中有任意一个非零,则条件为true。 |
|| | c语言风格逻辑或运算符。如果两个操作数中有任意一个非零,则条件为true。 |
not | 逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将使其为false。 |
两种风格都可以使用。
列表和数组
在Perl里代表复数的是列表和数组。
列表是标量的有序集合,而数组则是存储列表的变量。
列表指的是数据,而数组指的似乎变量。每个数组变量都一定包含一个列表。
用于列表和数组的操作有许多是相同的,后面一般使用数组这个词。
数组是有序的。
数组可以包含数字、字符串、undef
和它们的组合。
数组的每个元素都是单独的标量变量(可以需要使用$
):
#!/usr/bin/perl
use utf8;
use warnings;
@list = (); # 定义空的数组
$list[0] = 1; # 数组中的元素赋值
$list[1] = 2;
$list[2] = 3;
print @list;
@list
中的@
表示引用整个数组,在定义和使用整个数组时需要用到。
数组的名字空间和标量的名字空间是分开的,所以程序中可以取相同的名字,不过最好不要这么做。
#!/usr/bin/perl
use utf8;
use warnings;
$Var = 1;
print $Var; # 打印1
@Var = (1, 2, 3);
print @Var; # 打印123
访问元素
如果对超过数组尾端的元素进行赋值,则数组会自动扩大,中间没有赋值的元素就是undef
。
数组的最后一个元素索引是$#XX
,XX
是数组变量名,不过这个用起来比较麻烦,用的更多的是负值索引:
#!/usr/bin/perl
use utf8;
use warnings;
@list = (); # 空的数组
$list[0] = 1; # 数组中的元素赋值
$list[1] = 2;
$list[2] = 3;
print $list[$#list]; # 3
print $list[-1]; # 3
print $list[-2]; # 2,也可以使用-2这种负值索引
一般情况下值用-1
来访问最后的元素,其它的负值不怎么用。
数组直接量
数组直接量用小括号包含,中间用逗号隔开成员,还可以使用...
:
#!/usr/bin/perl
use utf8;
use warnings;
print (1, 2, 3, 4, 5); # 打印12345
print (1...5); # 打印12345
print (5...1); # 打印空,因为...前后必须是向上计数的
字符串直接量:
#!/usr/bin/perl
use utf8;
use warnings;
print ("hello", " ", "world");
为了避免键入太多的引号,可以使用qw()
函数:
#!/usr/bin/perl
use utf8;
use warnings;
@list = qw(hello world);
foreach $value (@list) {
print $value;
print "\n"
}
qw()
会将内部元素转换为字符串,其中的空白符是隔断,并且最终会被抛弃。
数组元素的赋值:
#!/usr/bin/perl
use utf8;
use warnings;
($name, $age) = ("Jake", 19);
print "The name is $name\n";
print "THe age is $age\n";
操作函数
push()
函数将新元素添加到数组尾部。
pop()
函数取出数组最后一个元素并将其作为返回值返回。如果数组本来就是空的,则返回undef
。
shift()
函数将新元素添加到数组开头。
unshift()
函数取出数组第一个元素并将其作为返回值返回。
splice()
函数在数组中间添加或移除某些元素。它接收4个参数:
- 目标数组;
- 要操作的开始元素的索引;
- 元素长度,可选,如果不指定,则默认长度到最后一个元素为止;
- 替换用的数组,可选,如果不指定,就是删除目标数组的数据。
如果元素长度为0,替换用的数组存在,则相当于添加数组。
上述的函数都会改变数组本身。
reserve()
函数返回次序相反的数组,但是不改变原数组。
sort()
函数返回排序之后的数组,但是不改变原数组。
上述两个函数不改变数组本身。
字符串中的数组内插
数组内插也可以在字符串中使用,所以需要注意下面的情况:
#!/usr/bin/perl
use utf8;
use warnings;
@google = qw(hello google);
print "perl@google.com\n"; # 打印perlhello google.com
print "perl\@google.com\n"; # 打印perl@google.com
foreach/each
foreach
用于遍历数组,它包含两个参数,第一个是数组变量,第二个是数组本身,它需要用括号括起来:
#!/usr/bin/perl
use utf8;
use warnings;
@name = qw(Jack Jimmy John);
foreach $n (@name) {
print "The name is $n\n";
}
如果省略数组变量,则默认用$_
替代:
#!/usr/bin/perl
use utf8;
use warnings;
@name = qw(Jack Jimmy John);
foreach (@name) {
print "The name is $_\n";
}
更特殊的是,即使print()
函数什么也不带,也会打印$_
:
#!/usr/bin/perl
use utf8;
use warnings;
@name = qw(Jack Jimmy John);
foreach (@name) {
print;
} # 最终打印JackJimmyJohn
each
以前只能用于哈希,但是现在也能用于数组,它返回数组中下一个元素对应的键值对(对于数组就是索引和元素值):
#!/usr/bin/perl
use utf8;
use warnings;
@name = qw(Jack Jimmy John);
while (($index, $value) = each @name) {
print "$index: $value\n";
}
数组输入
标量输入是一行,以回车键结尾;数组输入是所有行,以EOF
结束(Linxu中是Ctrl+D
,Windows中是Ctrl+Z
):
#!/usr/bin/perl
use utf8;
use warnings;
chomp(@line = <STDIN>);
print @line;
chomp()
可以去掉每一行末尾的换行符。
子程序
子程序其实就是函数。
使用关键字sub
定义函数:
#!/usr/bin/perl
use utf8;
use warnings;
sub add {
$n += 1;
print "value: $n\n";
}
add # 1
add # 2
add # 3
&add # 4,注意这里使用了&
从这里也可以看到$n
实际上是全局的。
注意这里的&add
前面的&
,这里是先定义了函数,然后调用,所以Perl能够识别到函数,可以不用&
,但是这可能跟Perl内置函数冲突,所以最好还是加上&
。
返回值
在Perl中所有函数都有返回值,返回的是最后一次运算的结果(不管是什么):
#!/usr/bin/perl
use utf8;
use warnings;
sub add {
$n += 1;
}
print &add; # 1
print &add; # 2
这里$n += 1;
的计算结果就是返回值。注意下面的代码:
#!/usr/bin/perl
use utf8;
use warnings;
sub add {
$n += 1;
print $n;
}
print &add; # 1
print &add; # 1
这里的两个print &add;
打印的值都是1,原因是函数add()
返回的是print()
的结果,后者的返回值一般就是1。
返回值也可以是非标量。
返回值也可以使用return
操作符,尤其是要在函数中间返回的时候,不过这个时候return
后面需要加返回值。如果什么也不写,则在标量上下文返回undef,在列表上下文返回空列表。
参数
函数可以有参数,用小括号括起来。参数其实是一个列表,在函数中用@_
访问,但是要使用其中的元素,还是应该用$_[x]
:
#!/usr/bin/perl
use utf8;
use warnings;
sub sum (a, b) {
foreach (@_) {
print "$_\n";
} # 打印1和2
$sum = $_[0] + $_[1];
}
print &sum(1, 2); # 打印3
@_
在标量上下文中也可以是标量,表示参数的个数:
#!/usr/bin/perl
use utf8;
use warnings;
sub max {
if (@_ != 2) {
print "Invalid parameter\n";
return;
}
if ($_[0] < $_[1]) {
print "2ed is bigger\n";
} elsif ($_[0] > $_[1]) {
print "1st is bigger\n";
} else {
print "equal\n"
}
}
&max(1); # Invalid parameter
&max(1, 2); # 2ed is bigger
&max(1, 2, 3); # Invalid parameter
参数也可以通过shift
传递:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
sub print_param {
$first = shift;
$sencond = shift;
say $first;
say $sencond;
}
&print_param(1, 2);
每调用一次shift
就可以用参数赋值一次。
私有变量
函数中的变量一般是全局变量,如果要使用私有变量,需要使用my
操作符:
#!/usr/bin/perl
use utf8;
use warnings;
sub add {
my $n += 1;
}
print &add; # 1
print &add; # 1
由于$n
是私有变量,所以每次调用函数都会重启被初始化为undef
,然后加上1,就变成了1,最终两次返回都是1。
如果想要把私有变量保存起来(类似于c语言中的statisc
),则需要使用state
操作符:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010; # 注意state是在5.10版本引入的,所以要加上
sub add {
state $n = 0;
$n += 1;
}
print &add; # 1
print &add; # 2
my不会改变赋值时的上下文:
my ($num) = @_; # 列表上下文,和 ($num) = @_; 相同
my $num = @_; # 标量上下文,和 $num = @_; 相同
在my
操作符不加括号时,只能用来声明单个词法变量:
my $fred, $barney; # 没有将$barney私有
my($fred, $barney); # 两个都私有了
my
可以用在数组上,或者foreach
里面。
全局变量
与my
对应的有一个全局变量,通过our
声明:
our %file_types = qw/
RAW 0x01
FREEFORM 0x02
SECURITY_CORE 0x03
PEI_CORE 0x04
DXE_CORE 0x05
PEIM 0x06
DRIVER 0x07
COMBINED_PEIM_DRIVER 0x08
APPLICATION 0x09
SMM 0x0A
FIRMWARE_VOLUME_IMAGE 0x0B
COMBINED_SMM_DXE 0x0C
SMM_CORE 0x0D
DEBUG_MIN 0xe0
DEBUG_MAX 0xef
FFS_PAD 0xf0
/;
还有一个local
。在一个全局变量上使用local操作符的时候,在每次执行local的时候都给该全局量一个临时值,但是这并不影响该变量的全局可视性。当程序抵达动态范围的末尾时,临时值被抛弃然后恢复原来的值。
输入和输出
读取标准输入
while
循环中的行输入简写:
#!/usr/bin/perl
use utf8;
use warnings;
while (<STDIN>) {
print "I saw $_";
}
foreach
也可以使用:
foreach (<STDIN>) {
print "I saw $_";
}
需要注意两者有明显的区别,foreach
需要将输入全部读取出来然后再显示,而while
是一行一行地读。所以对于内容较多的情况(比如读大文件),应该使用while
而不是foreach
。
钻石操作符<>
<>
是行输入操作符的特例,但是它并不是从键盘取得输入,而是从用户指定的位置读取:
#!/usr/bin/perl
use utf8;
use warnings;
while (defined($line=<>)) {
chomp($line);
print "$line\n";
}
执行脚本,并加上参数:
E:\Gitee\perl\Code\Basic>Input.pl Input.pl
#!/usr/bin/perl
use utf8;
use warnings;
while (defined($line=<>)) {
chomp($line);
print "$line\n";
}
实际上就是打印Input.pl脚本的内容。如果不加参数,就还是从标准输入获取数据。所以不加参数时,chomp()
就是作用在$_
上。
钻石操作符是用来读取输入的,而输入的内容可以在$_
中找到。
钻石操作符的入参来自@ARGV
数组:
#!/usr/bin/perl
use utf8;
use warnings;
@ARGV = qw# Input.pl #;
while (<>) {
chomp;
print "$_\n";
}
这里直接将入参写到了@ARGV
中,所以不需要在调用脚本的时候指定,作用还是打印Input.pl这个文件。而且即使加了入参也没有用,因为还是会在代码中被覆盖掉。
say
say()
跟print()
类似,但是会自动加上换行符。
printf
perl也支持格式化输出。
#!/usr/bin/perl
use utf8;
use warnings;
$name = "world";
printf "Hell %s\n", $name;
文件句柄
文件句柄就是程序里代表Perl进程与外界之间的IO联系的名称。
Perl有6个特殊的句柄:
STDIN
STDOUT
STDERR
DATA
ARGV
ARVGOUT
建议使用大写来命名文件句柄。
使用open()
来打开句柄:
#!/usr/bin/perl
use utf8;
use warnings;
open INPUT_PL, "< Input.pl";
while (<INPUT_PL>) {
chomp;
print "$_\n";
}
还是打印Input.pl文件内容。文件句柄的使用还是跟之前的STDIN
类似,就是<>
加打开的句柄名称。
注意open()
中有使用到括号:
<
表示只读;>
表示创建新文件;>>
表示追加;
注意在括号和后面的文件之间最好加上空格,避免出现意外,比如:
my $selected_output = ">password";
open LOG, ">$selected_output";
这样就导致变成追加了,而这可能不是想要的结果。
还有一种办法是直接采用下面的三个参数的写法:
open INPUT_PL, "<", "Input.pl";
这样就不用考虑因为没有空格导致的意外了。此外使用三种参数的情况还可以修改中间的第二个参数:
open INPUT_PL, "<:encoding(UTF-8)", "Input.pl";
这里增加了编码方式,当然第二个参数还有其它的参数可以加,这里不多介绍。
还可以用bitmode
以二进制方式读写文件句柄。
关闭句柄使用close()
:
close INPUT_PL;
句柄可以判断其真假,真表示打开成功,假表示打开失败:
my $fd = open INPUT_PL, "<:encoding(UTF-8)", "Input.pl";
if (! $fd) {
die "Open file failed. $!\n"
}
die()
函数会输出指定的信息,并且让程序立即终止并返回不为0的退出码。$!
是可读的系统错误信息,比如:
E:\Gitee\perl\Code\Basic>Input.pl
Open file failed: No such file or directory
只有系统服务请求失败后的瞬间,$!
的值才有用。
还有一个类似的warn()
,不过它不会立即退出程序。
上面是输入的例子,还是输出的例子:
#!/usr/bin/perl
use utf8;
use warnings;
my $fd = open INPUT_PL, "<:encoding(UTF-8)", "Input.pl";
if (! $fd) {
die "Open file failed: $!\n"
}
my $fd_tmp = open INPUT_TMP_PL, ">:encoding(UTF-8)", "Input_tmp.pl";
if (! $fd_tmp) {
die "Open file failed: $!\n"
}
while (<INPUT_PL>) {
print INPUT_TMP_PL $_;
}
close INPUT_PL;
close INPUT_TMP_PL;
注意这里的print INPUT_TMP_PL $_;
,输出句柄和输出内容之间没有逗号。
从本地获取字符串
my $usage = <<"END";
Usage:
$0 -o output.ffs [options] file.efi [...]
Options:
-h | -? | --help This help
-o | --output output.ffs Output file (default is stdout)
-n | --name FileName Name to include in UI Section
-t | --type Type FREEFORM|DRIVER|SMM|DXE_CORE|SMM_CORE|PEIM
-v | --version 1.0 Version section
-g | --guid GUID This file GUID (default is hash of Name)
-d | --depex 'guid guid..' Optional dependencies (all ANDed, or TRUE)
-z | --compress Enable LZMA compression
-a | --auto Detect the section types from the filenames
END
say $usage
这里的$usage
从本地获取字符串,<<
之后是结束符,所以这里就是将后面到END
为止的所有数据都当成了字符串赋值给$usage
。
哈希
数组是以数字来索引,而哈希则以名字来索引。即哈希的键是字符串,要求是该字符串必须是唯一的。
哈希没有顺序。
一个空的哈希:
%some_hash = ();
要指代整个哈希,可以用%
作为前缀。
给哈希赋值:
$some_hash{"name"} = "Jack";
$some_hash{"age"} = 18;
$some_hash{2} = "Hello world";
访问哈希使用{}
,这里实际上是赋值。
注意第三个$some_hash{2}
,这里的2会被转换成字符串。
哈希也可以自带元素:
%some_hash = ("name", "Jack", "age", 18, 2, "Hello world");
这里的个数需要是偶数的,按照**(键, 值, 键, 值, …)**的方式排布。哈希也可以展开成列表:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
%some_hash = ("name", "Jack", "age", 18, 2, "Hello world");
@array = %some_hash;
foreach $n (@array) {
print "The element is $n\n";
}
使用列表方式来初始化哈希不太方便,所以引入了=>
:
%some_hash = (
name => "Jack",
age => 18,
2 => "Hello world",
);
注意这里可以键可以省略引号。同样{}
中检索用的键名也可以不用引号。
哈希赋值
哈希可以直接赋值:
%some_hash = (
name => "Jack",
age => 18,
2 => "Hello world",
);
%new_hash = %some_hash;
更常用的方式是根据现有哈希得到新的哈希:
%some_hash = (
name => "Jack",
age => 18,
2 => "Hello world",
);
%new_hash = reverse %some_hash;
reverse()
实际上是(键-值)换成了(值-键)。
哈希函数
keys()
函数返回哈希的键列表。
values()
函数返回哈希的值列表。
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
%some_hash = (
name => "Jack",
age => 18,
2 => "Hello world",
);
my @k = keys %new_hash;
my @v = values %new_hash;
say @k;
say @v;
each()
函数每次调用返回哈希中的包含一对键和值的列表。
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
%some_hash = (
name => "Jack",
age => 18,
2 => "Hello world",
);
while (($key, $value) = each %some_hash) {
say "$key => $value";
}
exists()
函数检查哈希中是否存在某个键。
delete()
函数从哈希中删除指定的键及其相对应的值。
%ENV哈希
%ENV
是一个哈希,包含环境变量,比如:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
say "PATH is $ENV{PATH}";
注意这里引号里面的%ENV
里面用的是$ENV
,实际上是一种双引号中的内插。
控制结构
if/unless
if
是为真时执行,unless
是为假时执行。
有elsif
,注意只有一个e
。
when/until
when
是为真时执行,until
是为假时执行。
for/foreach
foreach
和for
这两个关键字实际上是等价的,但实际上foreach
和for
的用法还是分开的。
for
的用法:
for ( init; condition; increment ){
statement(s);
}
如果for
里面有两个分号,对应的就是真正的for,就是上面的用法,而下面的用法实际上对应的是foreach
:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
for (1...10) {
say "The number is $_";
}
而对应的真正的for
的用法:
for ($i = 1; $i <= 10; $i++) {
say "The number is $i";
}
注意say()
字符串中的参数引用,前者是$_
,后者是$i
。
循环控制
last
,类似于c语言中的break
。
next
,类似于c语言中的continue
。
还有一个redo
,会重新执行当次迭代。
定义或
这是一个操作符,对应//
,它在发现左边的值属于已定义时进行短路操作:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
my $name;
printf "%s", $name; # 会告警,因为变量不存在
printf "%s", $name // "No name";
简写
控制语句可以简写,比如:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$value = 1;
say "value is larger than 0" if $value > 0; # 倒置的if
虽然if
语句在后面的,但是也会先执行判断。
部分求值操作符可以用于简写:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$value = 1;
($value < 0) || say "value is larger than 0";
($value < 0)
为假,所以say()
函数会执行。
Perl模块
绝大部分Perl代码都是以模块的形式存在。
Perl模块有两个来源,一种是随Perl发行版本一同打包;另一种则需要从CPAN下载并安装。
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
use File::Basename;
my $name = "/usr/local/bin/perl";
print basename $name;
使用use
来包含模块。后面还可以增加导入列表:
use File::Basename qw/ basename /;
这里就只导入了basename
。也可用()
:
use File::Basename ( "basename" );
可以导入空:
use File::Basename ();
不知道有什么用。
Getopt
脚本参数可以通过Getopt
获取:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
use Getopt::Long;
my $input = "Unknown";
my $output = "Unknown";
GetOptions(
"o|output=s" => \$output,
"i|input=s" => \$input,
) or die "Can't get options";
say $input;
say $output;
直接脚本的示例:
E:\Gitee\perl\Code\Basic>Option.pl -i nihao -o hello
nihao
hello
文件测试
Perl提供了一组用于测试文件的操作符,它们大部分返回布尔值:
操作符 | 描述 |
---|---|
-A | 文件上一次被访问的时间(单位:天) |
-B | 是否为二进制文件 |
-C | 文件的(inode)索引节点修改时间(单位:天) |
-M | 文件上一次被修改的时间(单位:天) |
-O | 文件被真实的UID所有 |
-R | 文件或目录可以被真实的UID/GID读取 |
-S | 为socket(套接字) |
-T | 是否为文本文件 |
-W | 文件或目录可以被真实的UID/GID写入 |
-X | 文件或目录可以被真实的UID/GID执行 |
-b | 为block-special(特殊块)文件(如挂载磁盘) |
-c | 为character-special(特殊字符)文件(如I/O 设备) |
-d | 为目录 |
-e | 文件或目录名存在 |
-f | 为普通文件 |
-g | 文件或目录具有setgid属性 |
-k | 文件或目录设置了sticky位 |
-l | 为符号链接 |
-o | 文件被有效UID所有 |
-p | 文件是命名管道(FIFO) |
-r | 文件可以被有效的UID/GID读取 |
-s | 文件或目录存在且不为0(返回字节数) |
-t | 文件句柄为TTY(系统函数isatty()的返回结果;不能对文件名使用这个测试) |
-u | 文件或目录具有setuid属性 |
-w | 文件可以被有效的UID/GID写入 |
-x | 文件可以被有效的UID/GID执行 |
-z | 文件存在,大小为0(目录恒为false),即是否为空文件 |
可以同时测试多项属性:
if (-r $file and -w $file)
还可以连用:
if (-r -w $file)
文件测试函数
文件还有许多属性没有对应的操作符,这就需要使用到stat()
和lstat()
函数:
my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($filename);
这里的几个time都是时间戳,可以通过localtime()
函数转换成容易阅读的形式:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$filename = "File.pl";
my($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) = stat($filename);
say $ctime;
my($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst) = localtime $ctime;
printf "%d-%d-%d %d:%d:%d", $year + 1900, $mon + 1, $day, $hour, $min, $sec;
注意这里的时间起点是1900,月份起点是0,所以需要加上相关偏移值。
time()
返回时间戳,以秒为单位。
目录操作
文件通配
跟shell下的通配类似,Perl下使用glob()
来通配:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
my @all_file = glob "*.pl";
foreach (@all_file) {
say $_;
}
打印当前目录下的所有Perl文件。
通配符的另一种用法:
my @all_file = < "*.pl" >;
因为glob()
出的比较晚,所以就用上了<>
。
目录操作函数
chdir()
改变当前的工作目录。
opendir()
打开目录句柄。
readdir()
读取目录内容。
unlink()
删除文件。
rename()
重命名文件,甚至还可以移动文件:
rename "Control.pl", "../Control.pl";
这里将文件移动到了上一层。还有一种写法:
rename "Control.pl" => "../Control.pl";
mkdir()
创建目录。
rmdir()
删除目录。非空目录需要先删除里面的文件。
chmod()
修改文件或目录的权限。
chown()
修改文件或目录的拥有者以及其所属组。
字符串和排序
index()
找到字符串首次出现的位置:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
$small_string = "wor";
$where = index($big_string, $small_string);
say $where; # 6,计数从0开始
index()
找到字符串最后一次出现的位置:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
$small_string = "l";
$where = rindex($big_string, $small_string);
say $where; # 9,计数从0开始
substr()
从大字符串中获取到小的字符串,它接收三个参数:
- 大字符串;
- 其实数值,从0开始;
- 小字符串的长度。
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
say substr($big_string, 1, 2); # el
如果第三个参数不写,则表示获取到结尾为止:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
say substr($big_string, 1); # ello world
假如加上第三个参数后超过了大字符串的长度,那也只是获取到大字符串的结尾为止。
第二个参数可以是负数,表示从大字符串的结尾开始来计算起始位置,-1
就是最后一个字符:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
say substr($big_string, -1); # d
substr()
取出来的值可以直接修改:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$big_string = "Hello world";
substr($big_string, 0, 5) = "Goodbye";
say $big_string; # Goodbye world
sprintf()
可以返回格式化字符串:
#!/usr/bin/perl
use utf8;
use warnings;
use 5.010;
$money = sprintf "%.2f", 2.456;
say $money; #2.46