这里是我学习Perl的笔记,需要你了解Linux Bash脚本语言,Python语言的语法和常用的数据结构。
如果你没有这些知识,也可以点进来看看我推荐的Perl书籍(在开头就有)。
Perl 的介绍
什么是Perl
Perl是一门广泛运用在Linux系统上的脚本语言。在Python出现之前Linux系统上大家基本上都使用Perl进行出现编写。比起其他语言,Perl的优势在于文本和表格的处理。Perl也一度在网络编程中占据重要优势,以前的CGI程序大部分都是Perl写的,以前的运维也必须学习Perl。
虽然现在很多的语言抢走了Perl的工作,但是学习Perl无疑可以让我们在Linux系统上工作的更好。Perl和C语言一样,是大多数Linux系统自带的语言。
Perl适合做什么?
Perl非常适合编写一次性程序,如果你现在有个问题,但是又不想太花时间使用C/C++的话,Perl是个好选择(我甚至觉得在这方面Perl比Python的表现还要好)。
Perl也很适合编写快速的原型和文字处理程序。文字处理是Perl的强项。
其实除了文字处理,Perl也可以干很多事情。和Python一样,Perl有被称为CPAN的包社区,里面有处理各种情况的包。
Perl不适合做什么
首先和Python一样,由于是脚本语言,Perl没办法生成二进制文件,要想发布程序,必须连同源码发布出去。
其次,大程序和工程可能并不适合使用Perl来编写,因为Perl的语法问题。。。
使用的Perl版本
这里使用的是Perl5版本。6版本听说不受待见,而且据说改的像一门新语言,所以从5版本开始学习。
Perl的语法特点
Perl是很多语言语法的混合体,你可以在里面看到Bash脚本,C/C++,Python的影子。所以如果你语言见得比较多,学习Perl会非常容易。
学习Perl的资料
零基础入门推荐Perl三部曲,即骆驼书:
- 《Learning Perl》初级入门(中文《Perl语言入门》)
- 《Immediate Perl》进阶
- 《Mastering Perl》高级Perl
实践方面可以看《Perl最佳实践》
口袋书的话推荐《Perl 5 Pocket Reference》
Unix网络方面的话可以看《Perl网络编程》。如果你觉得使用C/C++进行Unix网络编程太麻烦的话(比如直接看《Unix网络编程》系列),那么我十分推荐看这本。因为Perl的网络编程API几乎和Unix的API一模一样,但是Perl使用起来却很方便,这有利于你免去复杂的结构体配置,快速熟悉Unix网络方面的API和编程技巧,等学完之后再去看《Unix网络编程》会更加得心应手。
另外,十分推荐在Linux系统上学习Perl。
Perl自带的帮助文档
如果你安装了Perl,那么你应该也安装过了perldoc工具。这个工具的功能和Linux下的man差不多,也就是perl语言的帮助文档。有什么不会的可以查一查。
开始Perl的学习
HelloWorld
|
|
和Python2很像对吧,第一行和Bash脚本类似,指定Perl的解释器在哪里。
第二行的print是函数。你也可以加上括号:print("Hello World!\n")
,但是如果解释器可以明确无歧义的判断参数类型话也可以不加括号。
我要怎么运行?
很简单,如果你写了第一行,那么直接将这个源代码文件变为可执行的,然后运行即可。
如果没有第一行,你就只能通过Perl解释器来运行,和Python一样:
|
|
Perl的文件可以不加后缀名,但是如果你要加的话,可以加pl,这也是大多数编辑器默认的Perl文件后缀。
分号是必须的吗?
对,是必须的。
区别大小写吗
区别
Perl的注释
Perl只有行注释,使用#
。
变量
创建变量
和Python一样,变量拿来就可以用,不需要声明,但是需要在前面加上$
:
|
|
这里要注意的是:当你在块里面(即花括号里面)和函数里面,甚至循环,判断结构中直接使用变量的话,这些遍历全部都是全局变量!
要想使用局部变量,必须使用my
关键字:
|
|
当然,变量仍然遵循就近原则:如果在内部声明了局部变量,那么全局变量会被隐藏。
使用warning(见下)会帮助你在局部块中新创建了全局变量时给你一个警告⚠️。
或者如果你想让Perl强制你使用my的话,可以使用严格模式:
|
|
这样即使在全局环境中你都得使用my关键字创建变量。
基本数据类型
标量数据
标量数据指数字
,字符串
,undef
。
数字
无论是小数还是整数,Perl一律视为双精度浮点数。
|
|
操作
四则和位运算是和C/C++一样的运算符,但是由于所有数字都视为浮点数,所以不存在整数除整数还是整数的情况。
字符串
使用双引号括起来的字符串内部可以转译。
使用单引号括起来的不能转译。
字符串可以内插,语法和Bash脚本一样:
|
|
这里和Bash一样,${str1}
中的花括号在没有歧义的时候也可以不加。
支持UTF8
Perl原生支持Unicode。如果想要支持UTF8编码,需要在代码开头(#!/usr/local/bin/perl
下面)加上
|
|
操作
最主要的操作是连接操作:
|
|
使用.
连接
也可以进行重复:
|
|
使用x
进行重复。
数字和字符串的相互转换
在Perl中,数字和字符串被视为一个东西。什么时候将值视为字符串,什么时候视为数字,取决于你使用的操作符。
比如3+2
显然就是数字之间的操作,直接得到5。然而'3'+2
这种情况也可以得到5。因为Perl会在需要数字的时候将字符串自动转换为整数(或者换句话说,因为+操作只能对数字有用,所以字符串会变为数字),而针对字符串的操作则会将数字变为字符串,如'3' . 2
得到'32'
。当转换字符串到数字时,Perl会从字符串开头的第一个字符开始看,直到遇到字符停止(小数点不算),也就是说:
|
|
所以3 x 2
将会是'33'
,因为3和2转换成字符串了。而3.2 x 2
会得到3.23.2
而且八进制和十六进制的前置0和0x技巧只对直接量有效,不能用于字符串转换:
|
|
想要知道数字和字符串对应的操作符?请参阅perlop文档(使用perldoc perlop
即可)。
害怕转换?请使用警告
如果你担心Perl的这种自动转换会给程序带来风险,你可以使用use warnings;
来让Perl在含糊不清的地方报一个警告。这样你就可以针对警告来修改你的代码。但是注意,抛出警告并不意味着Perl会自动帮你修改程序,它该怎么转换还是怎么转换。
也可以使用use diagnostics
来抛出更详细的警告。
数字和字符串的逻辑运算符
由于Perl会根据运算符来视值为数字还是字符串,所以对数字和字符串有不同的比较操作符。
数值的话使用常规的方法,字符串则是借鉴了Bash脚本的语法:
等于 | 不等于 | 小于 | 大于 | 小于等于 | 大于等于 | |
---|---|---|---|---|---|---|
数值 | == | != | < | > | <= | >= |
字符串 | eq | ne | lt | gt | le | ge |
undef
undef即未定义,和C的NULL,Python的None差不多。undef会被视为假。
对于undef的操作,主要是判断一个变量或表达式是否是undef。可以使用函数defined()
来判断。
列表(数组)和哈希
这两个结构的元素都是异质的,但是哈希的键必须是字符串。
列表(也就是数组啦)
Perl的列表是很宽泛的:
|
|
你可以像上面这样直接将普通变量当做数组,只需要在后面加上索引即可,而且索引是可以跳的。如果这个时候你使用$arr[1]
会得到undef。
任何可以求值得到数字的表达式都可以作为下标,如果不是整数,会自动舍去小数。
使用$#arr_name
来得到数组中最后一个元素的索引值。或者你可以像Python一样直接使用负数来得到最后一个元素。
列表直接量和数组引用
除了像上面那样一个一个费劲地放入元素,还可以使用列表直接量:
|
|
这里括号包裹的一堆东西就是列表。
如果想要使用列表这个整体(也就是引用列表),那么就必须使用@
符号放在前面。
使用范围操作符来生成数组
可以使用a..b
的形式来生成[a, b]
区间的所有整数:
|
|
列表之间的赋值
列表和列表之间可以赋值,但是你能否想到这种方式:
|
|
这里利用了列表赋值的特性,这个语句等价于:
|
|
如果你有不想要的元素,可以使用变量$_
代替:
|
|
列表的输出
可以通过print函数直接输出列表,但是列表的元素之间不会有空格分开,连在一起很难看。
解决办法是将列表内插到字符串中,这样在输出的时候就可以自动增加空格:
|
|
简便的纯字符串列表创造方法
使用qw
可以快速创建所有元素都是字符串的列表:
|
|
使用qw的好处是在于不用在字符串外面加上双引号了。
qw后面的括号可以换成其他的,比如方括号或者花括号。如果不是成对的字符就直接使用自己即可:
|
|
将列表当做栈
可以使用pop
和push
函数将列表当做栈,栈顶是列表的最后一位:
|
|
也可以使用shift
和unshift
,这个时候栈顶是列表的第一位。
在列表中间增加移动元素
使用splice
可以做到
|
|
第一个参数是要控制的列表,第二个参数是从哪个位置开始。
如果只给出上面两个参数,会返回这个列表的开头到pos的子列表。
第三个参数指出要返回的长度(注意并不是结束位置),第四个参数是在拿出元素之后要加的元素,以列表存储。
也就是说:
|
|
反转数组
使用reverse,这个在很多语言里面都有了,不讲了。
排序
使用sort
函数,排序的时候会将数字转换为字符串,然后按照ASCII码来对字符串排序。默认升序排序。
哈希
哈希和我们常说的哈希表不太一样。这里的哈希更像是Python中的字典,C++中的map。由于Perl中的哈希的内部存储是通过哈希表存储的,所以被称为hash。
哈希的键必须是字符串,值可以是任意数据类型。
访问hash
访问哈希和数组差不多,但是要使用花括号:
|
|
同时这也是给哈希赋值的一种方式。
和列表一样,你可以手动地给哈希的每一个元素像上面一样赋值。
如果访问了不存在的键,会返回undef。
访问整个hash
哈希的特有前缀是%
:
|
|
快速创建hash
列表可以转换为哈希,我们可以利用这种特性:
|
|
同样地,哈希也可以转换为列表:
|
|
但是不能保证列表中元素的顺序(但是相关联的键一定在其值前面)
或者你可以使用更加清晰的赋值方式:胖箭头:
|
|
其实胖箭头=>
在解释的时候会被解释为逗号,并且会将箭头左边的量自动视为字符串,所以这里的语法其实和上面的语法是一样的,而且你不需要在键上加上引号。当然在列表中你也可以使用:
|
|
不理解的话将胖箭头视为逗号即可。
那么这里有人会问了:这里的2,3,4,5是字符串吗?其实这个问题没有意义,因为字符串和数字是一个东西,是不是字符串取决于你怎么使用它。
hash函数
这里有一些常用的hash函数:
keys %hash
得到hash的键所组成的列表values %hash
得到hash的值组成的列表reverse %hash
将hash的值变为键,键变为值exists %hash, key
检查hash内是否有键keydelete %hash, key
删除hash内的key及对应的value。没有key的话不做操作。
和列表一样,你也可以使用each
来在迭代中同时得到键和值:
|
|
环境变量%ENV
%ENV
哈希是系统自带的哈希,表示当前的环境变量。
命令行参数变量@ARGV
也就是我们在C/C++中的int main(int argc, char** argv)
和Java中的void main(String[] argv)
中的argv了。存储命令行给出的参数的列表。
$_变量
Perl中有个很奇特的变量,$_
,当你的函数缺少参数,或者循环中缺少循环体的时候,这个变量就会自动补上。比如:
|
|
这里并没有指定循环遍历和print函数的参数,那么$_
变量就会自动补上。所以上面的代码等价于:
|
|
固有前缀
Perl中有一个令人迷惑的行为:列表和普通变量的名字可以是一样的!不仅如此,列表,变量,函数,字典等的名字都可以是一样的!那么如何区分它们呢?这就要使用前缀。每一个数据类型都有自己独有的前缀,比如变量就是$
,列表就是@
,然后字典是%
,函数则是&
。那么$a
就是a变量,而@a
就是a列表了。
引用
perl里的引用和C++的引用差不多,本质上就是C语言指针。
使用\
来表示引用:
|
|
这个时候$ref_arr就指向列表arr了。
引用其他的也是一样的:
|
|
解引用的话只要在引用前面加上固有前缀就可以了:
|
|
对于哈希和列表,也可以在不全部解引用的情况下使用内部元素:
|
|
控制结构
条件判断
If语句
if语句的格式和C差不多:
|
|
需要注意的有两点:
- 表示_else if_的语句是
elsif
- 不能像C语言一样,如果只有一条语句的话不加花括号。Perl里面条循环语句,条件语句都必须加花括号。
表示假的东西有:undef
,0和空的东西(空字符串,以及空列表,空哈希等)。
这里有一点需要注意:字符串'0'和"0"由于会事先被转换为数字0,所以字符串'0'和"0"也是假!
given-when结构
只能在5.10版本中及以后使用,所以你需要事先指定perl版本:
|
|
功能和C的switch差不多,语法如下:
|
|
given会将variable一个一个和condition匹配,如果成功了就会执行后面的代码。
和switch一样,如果你最后不加break,那么会导致继续向下匹配。
循环语句
while循环和unless循环
while循环是当条件为真的时候循环,而unless是当条件为假的时候循环:
|
|
for语句和foreach语句
for语句和C的一样,只不过在变量声明的时候需要遵循Perl的语法:
|
|
foreach语句必须在5.010版本下才能使用。
foreach可以让你像Python一样遍历可遍历结构,比如遍历列表:
|
|
也可以使用each
操作符来遍历列表,可以同时得到列表的下标和元素:
|
|
表达式修饰符
Perl有一个我很喜欢的功能,就是可以在表达式后面加一个控制行为的修饰符:
|
|
像这样,这样的话只有$n小于0的时候前面的语句才会执行。这种方法很简洁易懂。
你也可以在后面加上or:
|
|
这里如果chdir返回了表示假的值的话,会执行后面的print语句。这种语句广泛用于在函数出错的时候输出出错信息。
die和warn关键字
顾名思义,die关键字会让程序die,也就是让程序停止运行。相当于C/C++中的throw和assert(false)。die后面可以接一个字符串来让程序执行到die的时候输出这个字符串。也就是说在人工停止程序的时候给出一些信息:
|
|
配合上前面说的控制修饰符,我们可以这样写:
|
|
如果chdir函数出错,那么停止程序并输出出错信息。
warn的用法和die一样,只不过不停止程序,而是给出一个警告。
子程序(函数)
定义子程序
|
|
采用sub
关键字就可以定义。需要注意的是函数是没有参数列表和返回值类型的。
参数在哪里?
Perl使用@_
数组来存储参数。第一个参数存储在$_[0]
,第二个存储在$_[1]
,以此类推。
这就导致一个情况:你可以给一个函数任意多个参数。参数的不确定性进而引起一个IDE方面的特性:由于没有办法通过函数签名来知道函数有多少个参数,进而也就没办法在自动补全的时候告诉你参数的类型和个数。但有些IDE可以通过分析Perl文档来提示参数,但这也意味着你需要给你的函数写文档。。。
还有一个注意的地方:@_
符号是函数内的私有变量,所以出了函数也就没办法使用,除非你自己定义了全局的@_
。
有了这种得到参数的方法,变长的参数想必也是很容易实现了。
如何返回返回值?
你可以像C/C++等语言一样使用return
,但是Perl有一个更懒的方法:函数总会将其最后一行表达式的值返回(如果没有return的话)。这样,一来,不管你有没有return,函数总能返回值,只不过是返回的值有没有意义而已。
如果最后一行是函数调用,那么调用的函数的返回值将会被返回。
函数调用
像C/C++一样调用:
|
|
如果参数的类型不会因为上下文而导致歧义的话,也可以省略括号。
由于函数和变量可以重名,所以在调用无参函数的时候,你要么加上一对括号,要么使用&
符号前缀,以和普通变量区别:
|
|
简单来说,如果Perl可以判断出你调用的函数一定是函数,那么就可以省略&符号。
这里还有一个很重要的情况:用户自定义函数可以和系统自带函数重名!。在这个时候,如果你想要调用系统函数的话,必须在调用函数的前面加上&,不然默认调用的是用户自定义函数。
持久性私有变量
也就是C/C++中的静态变量,Perl里面使用state
关键字声明:
|
|
当然只能在函数内部使用。
函数出错了?看一下$!变量吧
如果函数的返回值表示函数出错了,你可以输出$!
变量来看看函数的错误信息(如果函数写了错误信息的话)。
后调用的函数会重新将错误信息放入$!
中(如果有错误的话),你也可以自己在函数出错的时候将信息写入$!中(但是我们默认不在函数成功的时候将成功信息放入$!中)。
正则表达式
Perl最强大的功能莫过于正则表达式了。虽然Python和C++,Java都带有正则表达式,但是Perl的正则使用方法是他们中最简单的。这也使Perl特别擅长对文字的处理。
如果你不会正则表达式的话,我推荐你去看看《精通正则表达式》(Mastering Regular Expression)。说实话,学Perl不用正则的话,很大程度体会不到Perl的方便。
如果你使用过vim,那么Perl使用正则的方式你会感到很熟悉。
使用正则表达式
和vim一样,使用/
将正则括起来,需要转译的字符用\
转译:
|
|
如果匹配成功,正则会返回真,否则返回假。像上面一样,如果不指定要匹配的字符,默认使用$_
里的字符匹配。
其实这是m//
的简写。和qw
语句一样,你也可以使用m{abc}
或者m!abc!
、当你不想加m的时候可以直接写//
。也就是说,上面的语句和
|
|
一个道理。
如果想要指定匹配的字符串,需要使用=~
符号:
|
|
模式分组
模式分组是正则里面的一个功能。简单来说就是你可以在正则表达式中使用已经匹配到的内容,比如:
|
|
这里圆括号表示一个分组,这里的分组会匹配到abc。然后使用\n
的方式引用第n个分组(分组从1开始),所以这里的正则表达式相当于:
|
|
当然你也可以:
|
|
这样会匹配两个连在一起的一模一样的字符,比如abba中的bb。
至于每个括号的分组编号是什么,很简单,从左往右看,第一个左括号代表的分组编号就是1,第二个左括号就是2,以此类推。
模式匹配修饰符
这部分知识属于正则知识,不再赘述,只是说一下Perl中常用的修饰符:
修饰符加载最右边:
|
|
- i:大小写无关匹配
- s:匹配任意字符(大部分情况下
.
号无法匹配换行符,使用这个来让.
可以匹配换行符 - x:加入空白符,加上x后可以在模式里面加入任意空白符(空格,制表符和换行回车符),所以原来的空白符Perl会直接忽略
模式中的内插
没错,正则中甚至可以内插变量:
|
|
当有歧义的时候,可以使用括号将变量单独括起来。
捕获变量
当你使用括号扩起一组正则的时候,Perl会匹配这个正则,并且留下来匹配的内容在$n
变量中,以便于你得到匹配的结果:
|
|
这个时候有三个组*a
,.+?b
,cca
。当你匹配的时候,匹配到的值会分别存入三个捕获变量$1,$2, $3
中来方便你获得。
捕获变量的值会一直存在,除非你手动改变它或有另一个正则匹配成功。注意,失败的匹配不会改变捕获变量,所以你应当只在匹配成功的时候使用捕获变量。
不捕获模式
如果你不想让每一个括号都影响捕获变量呢?可以使用(?:pattern)
的方法,在左括号右边紧接上?:
即可避免这个括号影响捕获变量。
命名捕获
如果你觉得$n
这种捕获变量的名字太难记了,对又对不上号,那么你可以给捕获组命名,匹配成功后会自动放入哈希%+
中:
|
|
使用(?<var_name>pattern)
的方式来指定名称,这样,第一个括号就不会影响到$1
,而是放入$+{name1}
中。
自动捕获变量
有三个系统自带的捕获变量:
$&
:匹配的部分会被存入- `$``匹配区段之前的部分会被存入
$'
:区段之后的部分会被存入
只要你在程序中使用过一次上述变量,Perl机会在所有正则中将对应部分存入这些变量。这将导致正则的效率变慢。所以这些变量最好不要使用。
替换
正则不仅能查找,也能替换。和vim一样,使用s///
的方式替换:
|
|
和vim一样,在最后加上g表示全局替换:
|
|
输入和输出
标准输入和输出
<STDIN>
,<STDOUT>
,<STDERR>
就是我们熟悉的标准输入,标准输出和标准错误流。在Unix中,你需要使用open函数打开,然后使用write函数写,read函数读。Perl也差不多,只不过不用打开而已:
|
|
上面的程序将用户的所有输入原样打在屏幕上。
print函数其实就是将ASCII写到<STDOUT>
中。你也可以使用printf
函数来格式化输出(和C语言的一样用)。
这个时候有人会问了:那我在print函数里面可以内插数组,那在printf里面怎么表示数组呢?很简单,假如@items
是一个有10个元素的数组,那么你需要:
|
|
这里首先使用x将%s
重复10次(在标量上下文中,直接引用列表将会得到列表的大小),然后和格式化字符串连接,最后放入items列表当做参数即可。
钻石操作符
<>
符号被称为钻石操作符,可以将给入的命令行参数所指的文件内容读入。也就是说:
|
|
会输出file1.cpp,file2.pl,file3.java的内容。也就是说钻石操作符首先会从命令行参数中取出第一个参数,然后打开这个参数指向的文件,将文件的内容返回(这里直接返回到$_中)。
在不同的上下文中其返回的结果也是不一样的,:
|
|
在列表上下文中会返回所有文件的内容,以行分割。
文件句柄
和其他语言一样,Perl有文件句柄。有6个文件句柄是Perl保留的:STDIN, STDOUT, STDERR, DATA, ARGV, ARGVOUT
。
在Perl程序打开的时候 ,print和say会和STDOUT关联,而warn和die会和STDERR关联。
Perl程序员一般将文件 句柄的名字全大写,以和普通变量区别。
文件句柄没有任何固定前缀,也就是说长这样:HANDLE
。
打开文件句柄(打开文件)
使用open
函数打开:
|
|
oepn不会返回文件句柄,而是将文件句柄赋值给第一个参数。
第二个参数是文件名称,如果你只是给入文件名称,那就是读(已存在)的文件,也就是C语言中的r
操作。
如果在文件名前加上>
表示以写的方式打开文件(C语言中的w)。
如果在文件名前加上>>表示以追加方式打开(C语言中的a):
|
|
在5.6版本之后,你也可以用更清楚的方式打开:
|
|
那么如何用二进制方式打开呢?这需要使用binmode
关键字:
|
|
也就是说,你先用open打开文件,然后再用binmode将其变为二进制读写。
如果你比较熟悉Unix的openAPI的话,你也可以使用sysopen
,这个函数的用法和Unix的open函数完全一样:
|
|
复制文件句柄
你也可以使用open来复制文件句柄:
|
|
关闭文件句柄
使用close函数关闭文件句柄。
读取文件
读取的话分为两种:一行一行读取和按字符读取。
一行一行读取的话和从STDIN读取数据一样:
|
|
上面的语句会将文件里的每一行读到$_中并输出
如果想要按字读的话,那么就使用read
和sysread
函数:
|
|
read和sysread的使用方法一样,将从开头偏移$offset,长度$length的内容读入$buffer中。而且注意这里的第一个参数后是由逗号的。
区别在于read是阻塞函数,除非读到文件末尾,否则会一直读length长度。而sysread是非阻塞的。这一点在对socket编程的时候由很重要的区别。
写文件
同样也是分一行一行写和写一堆。
一行一行的话直接用print,printf和say均可。具体做法是将文件句柄放在第一个参数的位置:
|
|
注意中间是没有逗号的。
如果要一次写一堆的话,使用函syswrite
就可以了:
|
|
这里注意,write函数和syswrite的功能是不一样的,不要混淆。syswrite是非阻塞的。
select函数
select函数有很多种情况,其中一个情况是只有一个参数:
|
|
这会改变print指向的句柄。也就是说print原本是指向STDOUT的,你可以将其改变为你的文件句柄,然后直接输出,这样内容就会到文件句柄中。
这个函数返回上一个句柄。
检测文件尾
使用eof(FHANDLE)
函数来检测文件末尾,如果下一次读取的时候到达了文件尾,那么会返回真。
使用面向对象的方法操作IO
Perl有一些模块可以通过面向对象的方法使用IO,常用的是IO::Handle
(对文件句柄进行面向对象)和IO::File
(对文件进行面向对象)。IO::File
比IO::Handle
用途更多。
要使用模块,首先的使用use
关键字:
|
|
对于IO::File
,我们给出一个例子:
|
|
具体的用法请见perl文档。
文件,目录操作
Perl的文件和目录操作基本上都是和Unix系统编程中同名的API函数,只不过需要在Perl的语法下使用而已。
文件测试
Perl可以得到文件的一些信息,以及测试文件的一些属性。
测试的方法和Bash脚本差不多:使用-X
(X是一个字符)来表示测试。比如-e
表示测试是否存在:
|
|
所有的测试符可以见这里测试符的最下面。
在5.10版本前,如果想要测试多个文件符,必须分开操作。在5.10之后可以将其放在一起:
|
|
stat和stat函数
stat是Unix系统编程的一个函数,perl里也可以使用这个函数。
这个函数的功能是返回文件的一些信息,包括uid,符号链接的数量等。
stat函数的参数可以是文件句柄,或者是文件名称。其返回一个含13个元素的列表(如果失败返回空列表):
|
|
localtime函数
localtime也是和Unix函数一样的。其参数为时间戳,返回值视上下文:
- 标量上下文:返回一个可读的表示时间的字符串
- 列表上下文:返回
($sec, $min, $hour, $day, $mon, $year, $wday, $yday, $isdst)
列表。其中$mon
是0-11的月份值,$year
是从1900年开始的年份,所以 你得加上1900年才能得到现在的年份。$wday
是从0-6的值,代表从周日到周六,$yday
表示目前是今年的第几天,从0-364.
目录操作
改变路径
使用chdir
即可改变路径:
|
|
文件名通配符
像Bash一样,可以使用*
来通配文件:
|
|
执行之后会打印出目录下的所有cpp文件。也就是说bash会帮助你展开通配符。
在Perl程序中也可以使用通配符,使用glob
函数即可:
|
|
glob的作用和Bash完全一样,所以不会列出开头为.的文件。
文件和目录的操作
这里列举一些常用的操作:
-
删除文件unlink:使用
unlink
删除,参数可以是以逗号分隔的文件名,或者直接是列表。返回删除成功的文件数目。 -
重命名文件rename:第一个参数是原文件名,第二个参数是新文件名
-
创建和删除目录mkdir/rmdir
-
修改权限chmod
-
修改隶属关系chown
-
修改时间戳utime
这些函数的用法可以在perldoc中找到。
进程,管道和信号
perl也可以操作进程,而且其API函数和Unix的API几乎一致,很容易上手。
进程
进程创建
和Unix一样,使用fork
函数创建。
fork函数会返回值,如果返回的是0,代表现在所在的进程是子进程(用fork创建的进程),而如果是大于0的,代表是刚刚创建的子进程的ID,也就是说现在仍然处于父进程:
|
|
发生错误返回undef
。
$$变量里面存储着当前进程的pid,这个变量时只读不可写的。
你也可以用getgpid([$pid])
来得到指定进程的进程组id,如果没有参数,返回当前进程的进程组id。
进程运行程序
当你使用fork之后,子进程和父进程还是使用一样的资源和代码。只有当你改变了子进程的代码或者改变了子进程的资源之后,子进程才会在内存中开辟一片内存。这种技术叫做写时复制(copy on write)。
然而,和父进程运行一样的代码有什么意思呢?所以我们在创建子进程之后基本上立刻就要使用exec()
函数来给子进程新的代码执行。exec的功能是以新的命令替换当前进程。所以exec从不返回,因为替换之后原来的代码已经没了:
|
|
所以现在有两种情况让子进程运行和父进程不一样的代码:
- 使用if语句判断fork的返回值,如果返回值为0代表是子进程,执行子进程专有代码
- 使用exec替换子进程原本的程序。
管道
就是Unix下的管道,可以通过Perl来打开。
使用open打开管道
使用open可以打开管道:
|
|
在文件名前面加|
表示打开读管道,在后面加|
表示打开写管道。
但是你不能同时打开读写管道:open FHANDL, "|filename|"
是错误的。
直接管道
可以像Bash命令一样直接使用\
``来打开管道:
|
|
这样context变量会得到ls命令的输出结果。
pipe函数
这个是Unix下的标准打开管道的函数:
|
|
其两个参数都是返回值参数,第一个参数为打开了的写管道,第二个参数为打开了的读管道。
一般来说管道是让两个程序通信的东西,所以在同一个程序里面打开管道没什么意义,除非你用的多进程程序。如果想要两个进程之间通过管道通信,一般是创建管道后一个进程关闭读/写管道,另一个进程关闭写/读管道,这样让一个进程向另一个进程传输数据。
虽然pipe返回两个管道,但是你不能保留两个管道同时进行读写操作。如果想要既读数据又写数据,必须再开一个管道,然后关闭其中的读/写管道。
管道的判断
使用-p
来判断一个句柄是不是管道,用-S
判断是不是一个套接字,用-t
判断文件句柄是不是又终端打开。
信号
信号种类
信号是Unix系统传输给程序的信息。比如你每次程序死循环的时候,你会按下Ctrl-C
来强制关闭程序对吧,这个时候其实是Unix发送了INT
信号给程序,INT信号的默认操作是关闭程序,所以程序会被关闭。
但是你也可以自己使用代码接受信号,然后自定义处理信号的方法。POSIX定义了19个信号:INT
,QUIT
,HUP
,KILL
,ILL
,SEGV
,PIPE
,ALRM
,CHLD
,STOP
,TTIN
,TTOU
,TSTP
,USR2
,USR1
,FPE
,ABRT
,COUNT
,TERM
.其中最常用的是:
- INT:来自键盘的中断,默认终止程序
- KILL,STOP:来自系统的中断,终止程序,不可捕获
- CHLD:子进程终止
- HUP:挂起
- PIPE:写往没有读取者的管道
- ALRM:来自闹钟的定时信号
其中KILL和STOP是不能够被捕获的,其一旦发出,程序必定被停止。
HUP用于挂起程序,其实很常用:在你用命令行转一个程序的时候,关闭了命令行之后程序并没有死亡,而是被挂起了(其实我以前一直以为是死亡了。。。)。
CHLD则是在程序的子进程终止的时候,其父进程会接收到这个信号。这个信号一般用来帮助我们解决僵死进程。
ALRM则是在使用alarm($second)
函数下,经过second描述之后程序会接收到的信号。
截取信号
你可以在%SIG
哈希中注册信号函数来截取信号:
|
|
这里截取INT信号,所以你在按下CTRL-C的时候不会终止程序,而是输出一行信息。
发送信号
使用kill()
函数发送信号:
|
|
第一个参数是要发送的信号,以字符串表示,如'INT'
。第二个参数是一组pid,表示你要发送信号的对象。返回能够被发送信号的进程数量。
使用信号处理程序的建议
由于信号可能在程序的任何时候到达,而一旦到达程序就会执行对应的信号处理程序。所以你的信号处理程序最好不要改动内存啊,做IO操作等花里胡哨操作,这样可能和程序原本代码冲突。最安全的方法是设置一个全局变量的状态,然后在主代码中检查这个状态来判断下一步做什么。