写在前面
Perl语言(https://www.perl.org/)最初是为文件体系处理而创作的一种多用途语言,Perl试图填补低级语言(如C、C 或汇编语言)和高级语言(如shell编程)之间的空白,使其既满足快速编程,又具有灵活的文本处理功能。Perl简单好用,但是比较难学,Perl为了提高运行速度,拥有大量简写、缩写,并拥有灵活的正则表达式系统,使得完成同一件任务可以有很多不同的方法。Perl脚本信息密度高,拥有很多浓缩语句(类似于普通语言里的“成语),因此可以用较短的代码完成更多的任务。一般Linux系统都会预安装perl,在Windows系统中运行Perl脚本则需要安装软件ActivePerl。
1.数据与操作符
1.1标量数据
标量(scalar)是Perl里面最简单的一种数据类型,用来称呼单个事物。这里的“单个事物”是指作为一个整体来进行处理的数据,可以是数字,也可以是字符串(例如"Hello"或者一篇文章)。标量数据可以使用操作符进行处理,产生的结果也为标量,标量可以储存在标量变量里。
⑴数字
数字有整数(不含有小数点)和浮点数(带有小数点)两种,但是在Perl里均采用双精度浮点数(double-precisionfloating-point)对所有数字进行储存和运算。
①直接量
直接量(literal)是指数字在Perl中的写法,对于浮点数如下所示写法均是正确的:
1.25
125.00
1.25e45 #指数,1.25×1045
1.25E45 #e也可以大写
-1.25e45 #负数
1.25e-45 #负指数
整数的直接量如下所示:
0
255
-255
61_298_040_283_768 #下划线分割很大的数使得数字更易读
非十进制的整数直接量:
0377 #八进制前置数字0,377等于十进制255
0xff #十六进制前置0x,ff等于十进制255,F可大写
0b11111111 #二进制前置0b,11111111等于十进制255
0x1377_0B55 #下划线分开使得大数字更易读
②数字操作符
Perl提供了各种常见的数字操作符也即运算符,如下所示:
2 3 #得5,加法
5.1-2.4 #得2.7,减法
3*12 #得36,乘法
10/3 #得3.3333…,除法
10.5%3.2 #得1,取余数(先取整再取余)
2**3 #得8,指数
(1 2)*3 #得9,结合运算
⑵字符串
字符串可以是各种字符的任意组合,最短的字符串不包含任何字符也即空字符串,字符串的长度没有限制(当然不能超过计算机内存)。字符串的构成可以来自ASCII编码32到126之间,此外Perl还可以使用更为强大的Unicode,但是必须加上utf8的编译指令。
①单引号内的字符串直接量
单引号内的字符串直接量指的是'…'内的一串字符,除单引号本身和反斜线外,单引号内的所有字符都代表他们本身,要表示单引号或者反斜线,在需要在前面再加反斜线来转义,如下所示:
'fred'
'hello
world' #直接使用换行
'Ican't see it' #反斜杠转义
需要注意的是单引号内不需要使用n来表示换行符。
②双引号内的字符串直接量
双引号内的字符串直接量指的是"…"内的一串字符,与单引号相比,双引号内更广泛的使用转义符号,这和shell变量是类似的,如下所示:
"fred"
"hellonworld" #n换行
"wecall it "Perl"" #转义双引号
"speciestabundance" #t制表符
"x{2668}" #转义Unicode字符中代码点为2668的字符
③字符串操作符
字符串连接符为“.”,如下所示:
"hello" . " " . "world" #等同于"helloworld"
字符串重复操作符为字母“x”,如下所示:
"fred"x (1 2) #等同于"fredfredfred"
5 x 4.8 #等同于"5555",注意不是5*4.8
"5" * "4.8" #得24
"5abc6" * "4.8" #得24
注意当使用重复操作符时默认左边是字符串,右边采用退一法取整,而使用数字运算符时默认两边都是数字,而且非字符串部分会被自动略过(如果是"fred"则转换为数字0)。可以看出Perl会根据需要灵活的进行数字与字符串的转换。
⑶标量变量
变量(variable)就是储存一个或多个数据的容器的名称,而标量变量,是存储一个标量的变量。标量变量的调用以“$”开头加变量名,变量名由多个字母、数字、下划线组成,例如$line_length。
Perl里面的变量赋值符号为“=”,等号左边为要赋值的变量名,右边为单个字符串、数字或其表达式,也可以是标量变量或其表达式,如下所示:
$fred =17;
$barney = 'hello';
$fred2 = $fred 3;
$barney2 =$barneyx 2; #字符串操作符空格不能省
$meal = "steak";
$fred_meal = "fred ate a $meal";
$fred_meal = "fred ate 3 ${meal}s"; #加花括号后可以直接连接字符
$fred = $fred 3; #等效于$fred =3,其他运算符类似
$barney = $barney .'world'; #等效于$barney .='world'
在操作或赋值中如果引入了一个从未被赋值的标量变量,则会视为空字符串或数字0对待,这时候这个新变量实际上拥有一个特殊的值undef,表示未定义。在Perl中变量因赋值而存在,不需要预先声明变量,在未赋值之前为undef,而赋值也是Perl中唯一的声明变量的方法。在Perl中可以使用defined函数来检查一个变量是否为undef,若是undef则返回值为假,其余均为真。
⑷操作符优先级与结合性
在复杂的表达式里,先执行哪个操作再执行哪个操作,即是优先级(precedence)问题,当连续出现优先级相同的操作符时,根据结合性(向左/向右)来判断哪一部分先进行计算。在Perl中,数字运算符的优先级与数学一样,例如2 3*4会先计算乘法,可以利用括号来改变优先级顺序:(2 3)*4,这样就会先计算加法。灵活的运用括号已避免忘记优先级带来的麻烦。常见操作符的优先级(从上到下)以及结合性如下所示:
$fred #单目操作符,等同$fred =1
在Perl中,单目操作符只对一个变量数据进行操作,双目操作符对两个变量数据进行操作,三目操作符对三个变量数据进行操作,如下所示:
!$fred #单目操作符,not$fred
'hello' . 'world' #双目操作符,连接
$x ? $y : $z #三目操作符
在等优先级操作符中,向右结合意味着先计算右边部分,向左结合则相反,如下所示:
4**3**2 #向右结合,等同4**9
72/12/3 #向左结合,等同6/3
1.2列表与数组
列表(list)指的是标量的有序组合,数组(array)是储存列表的变量,也即和标量数据与标量变量的关系一样,列表为数据,而数组为变量。数组或列表中的每个元素都是单独的标量变量,拥有独立的标量值,这些值都是有序的,每个元素都有相应的整数作为索引,此整数总是从0开始递增。
⑴创建与访问数组
数组的命名规则与标量变量类似,数组元素因赋值而诞生,我们在数组名称后面使用[]括起来的索引值进行赋值与访问,如下所示:
$fred[0]="abc";
$fred[1]="cde";
$fred[2]="fgh";
赋值后这时候就创建了一个名称为“fred”的数组,引用整个数组时,可以在名称前添加@符号,这个符号在这里代表“all”的意思。我们可以在同一个程序里为标量变量和数组变量取相同的名字,因为在Perl中不同类型的数据是储存在不同空间,也即$fred和$fred[0]毫无关联。
数组的元素可以灵活的运用标量数据中的操作符进行操作,任何能求得数字的表达式也可以用在索引里,如下所示:
$fred[1] .="hji";
$n=3.71828;
$fred[$n-1]; #等效于$fred[2]
只要内存允许,数组的长度没有上限,对已存在的数组元素赋值,就会覆盖原来的值,对索引数超过数组尾端的元素进行赋值,数组将会根据需要自动扩大,这时候没有赋值的元素将被赋值为undef,如下所示:
$fred[0]="abc";
$fred[1]="cde";
$fred[10]="fgh"; #此时数组有11个元素,其中8个为undef
数组最后一个元素的索引值为$#fred,对于前面的数组这里$#fred=10,因此最后一个元素的访问方法如下所示:
$end = $fred[$#fred]
在Perl中还可以使用负的索引值从数组末端开始索引,但是负数的绝对值不能超过数组长度,如下所示
$end = $fred[-1]; #与$fred[$#fred]等效
⑵列表直接量
列表直接量就是指在Perl代码中一列数据的写法,一般使用括号括起来,常见的如下所示:
() #空列表
(1, 2, 3) #包含1、2、3这三个数字的列表
("fred", 4.5) #包含两个元素"fred"和4.5
(1..100) #包含100个整数的列表,“..”取两端数字中间范围,每次加1,若两端小数自动取整,只能从小往大取,括号可省
(a..z) #包含a到z的26个小写字母
($m..$n) #也可使用变量及其表达式来界定范围
在Perl中,还可以使用qw(quoted word)简写创建字符串列表,这样可以省去很多引号、逗号的书写,如下所示:
qw(fredbarney betty Wilma) #等效于("fred", "barney", "betty", "Wilma")
perl会将qw内的字符当成单引号内的字符串进行处理,不能像双引号内一样引用变量表达式以及很多反斜杠转义的内容例如n。qw支持很多标点符号作为左右定界符,例如上面列表也可以写成:
qw! fredbarney betty Wilma !
qw<fred barney betty Wilma >
如果元素内容包含有定界符,可以使用来转义。
⑶列表赋值
在不指定索引时,列表可以用来批量给标量变量赋值,如下所示:
($fred, $barney, $dino) = ("abc", "def", undef)
($fred, $barbey) = ($barney, $fred) #也即交换了两个变量的值
在赋值的时候如果等号左边有多出来的变量,会被赋值为undef;如果等号右边有多出来的元素,会被忽略掉。当指定索引时,列表将作为一个整体储存在数组里面,如下所示:
($fred[0], $fred[1], $fred[2]) = qw/abc def ghi/
@fred =qw/abc def ghi/
@barney= ()
上述第一、二种写法是等效的,在等号左边也可以引入数组以及数组的表达式,如下所示:
@rock =(@fred, undef, @barney, $dino)
上述rock数组长度为fred和barney长度之和再加2,这里barney为空数组,不会给出任何赋值(注意和undef的区别),因此长度为0。
⑷数组操作符
对列表和数组,操作符更多样化,功能也更强大,Perl里面的操作符就类似于Bash里面的工具命令和R语言里的函数,但是Perl里面的函数之所以强调为操作符是因为其与R等语言的函数有很大不同,在Perl里面function(@array)会直接改变@array,而其返回值并不是改变后的@array,可以是函数操作拿出的值或者直接返回0或者1表示是否成功操作。
①pop和push操作符
如果要新增元素到数组尾端,只需要将新数据存放到更高的索引值对应新的位置即可,然而Perl在实际使用时很少使用索引值来对数组进行操作。一种更为简便的方法是把数组当成堆栈(stack)或列队来用,而pop(弹出)则是抽出数组尾端的元素,push(压入)在数组尾端添加新元素,pop的使用如下所示:
@array = 5..9; #创建数组array并赋值(5, 6,7, 8, 9)
$fred = pop(@array); #将array尾端9赋值给fred,数组array变成(5, 6, 7, 8)
$barney = pop@array; #数组array变成(5, 6,7)
pop@array; #数组array变成(5, 6)
可以看出使用pop操作数组时括号可以省略,在最后一行这里是空的上下文(void context),没有变量赋值,因此被取出的元素将被舍弃掉。接下来与之类似的是push的操作,push可以设置两个参数,第一个为要处理的数组,第二个为插入的元素或列表,不同参数之间逗号隔开:
push(@array, 0) ; #数组array变成(5, 6, 0)
push@array, 1..3; #数组array变成(5, 6,0, 1, 2, 3)
@others= qw/ null na/;
push@array, @others; #在array尾端添加others的元素
值得注意的是,pop对空数组进行操作会抽出undef。
②shift和unshift操作符
pop与push都是对数组尾端进行处理,shift和unshift则是对数组的开头进行处理。shift(移位)是移除数组开头的元素,unshift则是在数组的开头插入元素,shift的使用示例如下:
@array =qw# dino fred barney #;
$m =shift(@array); #取出第一个元素赋值给$m,数组array变成("fred", "barney")
$n =shift @array; #数组array变成("barney")
shift@array; #数组array变成空数组
可见shift与pop的用法很相似,unshift的使用示例如下:
unshift@array 1..3; #数组array变成(1, 2,3)
③splice操作符
除了对数组的首尾进行操作,还可以对数组中间的元素进行操作,这时候就需要用到splice(拼接)操作符,对数组中间元素进行删除或替换操作,splice最多可以接受4个参数,第一个是要处理的目标数组,第二个是操作起始元素位置,第三个是操作的长度,也即要操作的元素个数,第四个是要替换的列表。如果只输入前两个参数,splice会在给定位置提取元素直到末尾:
@array =qw( pebble dino fred barney betty );
@removed= splice @array, 2; #提取数组array第2个元素之后的元素,@array变成("pebble","dino"),@removed变成("fred","barney", "betty")
指定第三个参数后splice可以提取一定范围内的元素:
@array =qw( pebble dino fred barney betty );
@removed= splice @array, 1, 2; #提取数组array第1个元素之后的2个元素,@array变成("pebble","barney", "betty"),@removed变成("dino","fred")
指定第四个参数后会将提取的元素替换为给出的列表,替换的列表长度和拿走的列表长度不一定相同(也即这里不是元素的一一替换),假如第三个参数为0,splice会在指定位置插入新元素:
@array =qw( pebble dino fred barney betty );
@removed= splice @array, 1, 2, qw(wilma);
#拿走数组array第1个元素之后的2个元素并插入("Wilma")
#@array变成("pebble","Wilma", "barney", "betty")
#@removed变成("dino","fred")
@removed= splice @array, 1, 0, qw(yabba); #在第1个元素后插入("yabba"),@array变成("pebble","yabba", "Wilma", "barney", "betty"),@removed变成空列表()
在上面例子中splice从数组array中拿出元素并赋值给removed,这时候原数组少了元素,而数组removed结果为提取的元素而不是array的处理结果,splice相当于同时对两个数组进行操作,这与其他编程语言的逻辑是有很大差别的。
④reverse操作符
reverse会读取列表或者数组的值,并按照相反的次序返回列表,也就是逆序操作,具体如下所示:
@fred = 6..10; #数组fred为(6, 7, 8, 9, 10)
@barney = reverse(@fred); #数组barney为(10, 9, 8, 7, 6)
@wilma = reverse6..10; #数组wilma为(10, 9, 8, 7, 6)
reverse与前面的操作符不同之处在于,它会返回参数的逆序结果,但是不会修改参数(也即作用对象的列表或数组),因此如果没有赋值操作,直接使用此操作符是没有意义的,如下所示:
reverse@fred #错误,这不会修改数组fred
@fred =reverse @fred #正确,数组fred的元素顺序被倒置
⑤sort操作符
sort操作符读取列表或数组的值,根据内部字符编码顺序对元素进行反序并返回排序结果。sort默认按照Unicode代码点大小进行排序,如下所示:
@rocks = qw/bedrock slate rubble granite /;
@sorted = sort(@rocks); #@sorted为qw/ bedrock granite rubble slate /
@back = reversesort @rocks; #得到逆序后的排列结果
@number = sort97..102; #@number为(100, 101, 102, 97,98, 99)
在默认排序中,数字排在字母之前,大写字母排在小写字母之前,数字排序会按照从左到右按照每个位数进行排序,而不是数值大小。
1.3哈希
哈希(hash)是和数组类似的一种数据结构,与之不同的是哈希通过元素的名字作为索引,这里称为键(key),key可以是任意且唯一的的字符串。由于哈希不通过数字进行索引,因此元素是没有顺序的,哈希仅是很多键-元素值的对应集合,这些键与值可以是任意的标量,但是键总会被以字符串的形式储存。Perl语言中的哈希是从awk引入,但是进行了改良,使其可以任意大小,并且有良好的算法使得在数据量大时对哈希的访问速率不会变慢。哈希是Perl语言的关键特色,可以快速便捷的处理存在对应关系的文本数据。
⑴创建与访问哈希
哈希的命名与标量、数组类似,也可以由字母、数字、下划线组成,其创建与访问方式也与数组类似,只不过不是使用方括号而是花括号,例如我们可以创建一个哈希family_name,来储存名字与姓的对应关系(这里假设没有相同名字的),如下所示:
$family_name{'fred'} = 'flintstone';
$family_name{'barney'} = 'rubble';
在哈希的花括号里,也可以引入标量变量和数组及其表达式,如下所示:
$foo = 'bar'
$name{$foo} = 'ney'
$family_name{$foo. 'ney'} = 'rubble';
与数组一样,哈希元素因赋值而诞生,对已存在的哈希元素进行赋值,则会覆盖之前的值。如果使用哈希里不存在的key进行索引,将会得到undef。访问整个哈希,使用百分号%,因此我们之前创建的哈希为�mily_name。
⑵哈希赋值
哈希可以通过列表来进行赋值,列表中第一个元素为key,第二个元素为value,依此类推。例如上面创建的哈希的等同赋值形式为:
�mily_name= ('fred', 'flintstone', 'barney', 'rubble');
同样,我们也可以将哈希赋值给数组,这时候key与value的对应关系被展开为列表:
@array =�mily_name;
这种变换为展开哈希,但是得到的列表顺序不一定是('fred', 'flintstone','barney', 'rubble'),Perl为了实现哈希快速检索而对储存顺序做了特别的安排,因此元素顺序会被打乱,总之具有n个元素的哈希会被展开为具有2n个元素的数组。也可以将已有哈希赋值给新哈希:
%new_hash= %old_hash; %new_hash= reverse %old_hash;
第二条reverse会将哈希的展开列表进行逆序排列并赋值,这样就实现了key与value的互换,这种操作最好在key与value均唯一的情况下使用。
⑶胖箭头
胖箭头也即=>,使用列表对哈希赋值并不理想,如果出现错漏,后面的key-value对应将全部错误,因此可以在赋值的时候使用胖箭头来将key与value一一对应,如下所示:
代码语言:javascript复制�mily_name= (
'fred'=> 'flintstone',
'barney'=> 'rubble',
);
这样的组合看起来更加清晰,最后多余的一个逗号只是便于维护,在运行时Perl会将其忽略。哈希是一种比较复杂的数据结构,一般不会直接输出,对哈希的处理使用哈希函数(详见2.4)。
1.4标准输入与输出
与Perl代码的最简单互动就是通过标准输入与标准输出,来输入数据或者输出结果。
⑴print输出结果
print会将代码运行结果输出在屏幕上,一般我们使用双引号圈引print的内容,这是因为双引号内不因可以使用很多反斜杠转义,而且还可以使用变量内插,这里的变量可以是标量变量、数组、哈希,Perl不同语句之间使用分号分隔,print不同输出内容之间以逗号分隔,具体示例如下:
脚本第一行声明代码的解释器,不同计算机的安装路径可能会不一样,运行结果如下所示:
只输出变量的内容可以不加引号,但是输出数组时内插在双引号内可以在不同元素之间自动插入空格,当变量内插入单词内可以使用花括号隔离变量名。
在Perl 5.10及以上的版本中,可以使用命令say代替print,say命令会在输出的内容后自动添加换行符,而不需要在代码中添加。
⑵STDIN行输入
在Perl脚本中,可以使用行输入操作符<STDIN>来让Perl程序读取标准输入的数据(这里一般指键盘输入)。只要把<STDIN>放在脚本中希望返回标量值的地方,程序运行到这个地方就会停下来,等待键盘输入内容,直到换行符为止。具体示例脚本如下:
运行如下所示:
可以看到,虽然在使用键盘时换行键也即回车键被当成结束输入的命令,但是换行符还是被当成了标准输入的一部分,这时候可以使用chomp操作符去掉标准输入末尾的换行符,脚本优化如下:
再次运行如下所示:
chomp操作符还可以和<STDIN>连用,如下所示:
代码语言:javascript复制chomp($first_name =<STDIN>)