《Perl语言入门》学习笔记
0.前言
本文主要是记录perl学习中的一些关键知识点,尽可能帮助平日的脚本开发需要
1.标量数据
a 数字
perl内部对数字,统一都是按照双精度浮点数double类型来存储的,
b 字符串
最短的字符串是不含任何字符的空串,最长的字符串没有限制
字符串相关操作
- 分割:使用的是split函数,
split /,/ $str
等价于split(/,/, $str)
,返回的是数组 - 连接:字符串的连接使用的是符号
.
来进行,例如$newStr=$a . " " . $b;
- 重复:例如需要得到abcabcabc,那么可以根据字符串进行这样的操作
"abc" x 3
- 子串:substr()获取子串,substr(字符串,开始的索引位置,长度,替换的字符串)
- 长度:length()返回
- 比较:字符串的比较用的是
eq ne
perl中的字符串和数字会自动转换,具体情况是看使用了什么操作符
数字的飞碟操作符
a <=> b
表示对数字a和数字b进行比较,返回-1,0,1三种结果
2.变量
变量的类型主要是有这么几种
- 标量:$a = 1
- 数组: @a = (1, 2, 3)
- hash: %a = ('a' => 0, 'b' => 1, 'c' => 2)
perl的三种变量是存在不同的命名空间里,所以 $a, @a, %a 三者是不冲突的。但我们应该避免这样命名,可读性还是很糟糕的。
字符串标量内插
被双引号包裹的字符串,可以使用变量内插,这个和linux shell中的一样,例
"hello $name"
等价于 'hello' . $name
3.控制结构
if
这个与java中的一样,唯一不同的是,逻辑判断可以使用 and, not, or, xor, 还有 ne, eq 等等
值得注意的是,与js一样,if对不同类型的变量的布尔值处理也是有规律的
- 0 false, 其他数值都是true
- 值为字符串,空字符串是假,其他有长度的为真
- 既不是数字,也不是字符串,就会先试图转换为数字或者字符串
unless
unless 等于 if ( not xxxx) 这个样子
unless (a == 1) {
# do something
}
elsif
等于java中的else if
获取行输入
获取用户的行输入,使用的是句柄<STDIN>
,返回一个字符串,并且一般最后带有一个换行符。
$ilne=<STDIN>;
if ($line eq "\n") {
print "That was just a blank line!\n";
} else {
print "That line of input was $line";
}
chomp
只能用于字符串变量上,仅把换行符去掉
while
与java一样,真假值得判断和if中的一样
循环体中的几种控制符
last
等于break
next
等于continue
带标签的块
perl中建议使用大写来作为标签名,标签的使用时 标签名+冒号
LINE: while(<>) {
foreach (split) {
last LINE if /__END__/;
}
}
三目运算符
my $size =
($width <10 ) ? "small" :
($width <20 ) ? "medium" :
($width <30 ) ? "large" :
"extra-large"; #default
undef
如果没有赋值某个变量,那这个变量的的初始值是undef
- 当被作为数字使用时,为0
- 当被当做是字符串使用时,是空串
defined函数
该函数可以区分undef和空串,例如在读取文件的时候,当eof了,则会返回undef回来,这个时候就可以通过这个来判断了
$madona=<STDIN>;
if (defined($madona)) {
print "the input is $madona";
} else {
print "the input is empty\n";
}
4.列表与数组
数组下标从0开始,以此类推,最后一个元素的下标是-1。
列表直接量
需要引入列表的时候可以写成数组的样子,在圆括号中用逗号隔开的一系列值
(1,2,3)
("a","b",1)
(1..100)
qw
如果只是建立简单的单词列表,那么许多乏味的双引号和逗号会惹人讨厌。perl提供了qw的写法来简化
qw( a b c d e )
qw=quoted word,译为加上引号的单词,不论如何,perl会将里面当做是字符串来处理,qw的边界符不仅仅是括号,基本上成对出现的字符就行
qw ! a b c !
qw / a b c /
qw # a b c #
qw < a b c >
以上的几种写法都是合法的
列表赋值
( $a, $b, $c ) = ("111", "222", "333")
左侧列表中的三个变量,依次会赋予右侧列表的值
push pop shift unshift
push和pop对对数组的最后一个元素来进行操作
@arr=1..3
@brr=5..7
push(@arr, "1")
push @arr,@brr; # @arr得到@brr数组的所有元素
pop(@arr)
shift与unshift刚好相反,是对第一个元素来进行操作的
shift(@arr) # 弹出第一个元素
unshift(@arr,"1") # 将1添加到第一个位置
foreach
示例,每次循环迭代的时候,里面的控制变量是数组中的元素本身,任何修改都可以反映到本身。
@rocks=qw / 1 2 3 4 /
foreach $rock (@rocks) {
$rock = "\t$rock";
$rock .= "\n";
}
如果我们没有指定控制变量,那么会被默认存在$_
中
foreach (1..10) {
print "current number $_"
}
reverse sort
sort可以指定比较规则,比较规则是一个函数
sub by_ascii {
$a cmp $b;
}
my @strings = sort by_ascii @any_strings;
5.子程序
子程序更像是我们在其他语言中见到的函数,关键词是sub
开头的。子程序可以定义在任何位置,而不必要先声明。
参数,传给perl的子程序的参数,会被放入到参数数组@_
中,你可以通过$_[0]
这种来进行访问。
子程序可以通过检查@_
的长度,来检查是否传入的参数是否正确。
sub max {
if (@_ != 2) {
print "WARNNING...."
}
}
注意注意,@_
和 $_
是完全不同的,一个是数组长度,一个是在for遍历中代表当前元素的变量
私有变量
在没有特殊说明情况下,perl中定义的变量都是全局的,这显然不是一件好事,我们可以通过my
来指定变量的作用范围。
6.输入与输出
钻石输入符号<>
, 我们假设有如下的一个脚本my.pl
#!/usr/bin/perl
while (defined($line = <>)) {
chomp($line);
print "It was $line that I saw\n"
}
考虑到上面这段代码,钻石操作符是从文件或者标准输入流来读取文件的内容,我们创建一个临时文件,文件的内容如下
#/tmp/demo1
hahah
fdsafdsa
fdasfdsa
feiwqrf
然后我们执行命令perl /tmp/my.pl /tmp/demo1
得到的输出内容如下
It was hahah that I saw
It was fdsafdsa that I saw
It was fdasfdsa that I saw
It was feiwqrf that I saw
命令修改为perl /tmp/my.pl -
就变为了从标准输入来获取内容了
hahaha
It was hahaha that I saw
mymy
It was mymy that I saw
^C
注意,如果在使用钻石操作符去读取多个文件时,其实是这几个文件先被合并为一个大文件了,然后从头开始被顺序的读取下去。而当前正在处理的文件,会在变量$ARGV
中
#my.pl
#!/usr/bin/perl
while (defined($line = <>)) {
chomp($line);
print "FILE: $ARGV It was $line that I saw\n"
}
---------------------------------------------------------
FILE: /tmp/demo1 It was hahah that I saw
FILE: /tmp/demo1 It was fdsafdsa that I saw
FILE: /tmp/demo1 It was fdasfdsa that I saw
FILE: /tmp/demo1 It was feiwqrf that I saw
FILE: /tmp/demo2 It was 1111 that I saw
FILE: /tmp/demo2 It was 2222 that I saw
FILE: /tmp/demo2 It was 3333 that I saw
命令行参数列表@ARGV
文件句柄
my $fp;
open($fp, "</tmp/1.txt");
while (my $line = <$fp>) {
....
}
close($fp);
改变默认的文件输出
select SOME_FILE;
print "123\n";
一旦切换了默认的输出文件句柄,程序就会一直往那里进行输出。因此比较好的方式是,对不同的输出,进行指定。
select LOG;
$|=1; #这行的意思是清除缓冲区的数据
select STDOUT;
print LOG "I'm a log" # 指定输出的句柄
7.哈希
哈希就像是java语言中的map结构一样。
访问哈希
$hash{ $someKey }
哈希赋值
在访问哈希的基础上,右边添加一个值,就是已完成赋值了
访问整个hash
想要指代整个hash,就需要使用百分号作为前缀,hash可以被转换为列表,是一些简单的键值对列表
#!/usr/bin/perl
%demo_hash = (
"a" => 1,
"b" => 2,
"c" => 3,
"d" => 4
);
@demo_array = %demo_hash; # 这里会将hash转换为键值对
my $l = @demo_array;
print "@demo_array\n";
print "size=$l\n"
------ output --------
c 3 a 1 b 2 d 4
size=8
胖箭头
上面的方式来书写hash显然不是很方便和直观,于是就有了使用胖箭头的方式来进行
my %hash = (
"a" => "1",
"aa" => "2",
"aaa" => "3"
)
哈希函数
keys 和values
my @k = keys %hash; # 注意这里是要访问整个hash,不用$
my @v = values %hash;
each
如果需要罗列键值对,则可以使用each函数
while ( ($key, $value) = eash %hash ) {
print "$key => $value";
}
嵌套hash
$some_hash{a}{b}{c} = 1
exists函数
检查hash中是否有某个键
if (exists($hash{"a"})) {
# do something
}
delete函数
从hash中删除指定key的值,如果没有key,则程序会直接结束,而不会有任何的信息输出
%ENV
程序运行的环境变量,会在%ENV
可以获取到,print "PATH is $ENV{PATH}\n"
8.模块
使用命令来查询本地是否已经安装了某个模块,例如perldoc CGI
使用模块
use File::Basename
#....
导入模块与自定义函数冲突
有可能你导入的模块与自己定义的函数有着相同的名字,这个时候就需要指定导入的列表
例如你有定义一个函数dirname,而File::Basename
里面也有这个函数。
use File::Basename qw / basename /; # 引入basename函数
# or
use File::Basename qw/ /; #不引入任何函数
9.文件
文件测试符
操作符 | 意义 |
---|---|
-r | 对目前用户或组来说是具有可读的 |
-w | 对目前用户或组来说是具有可写的 |
-x | 对目前用户或组来说是可执行的 |
-o | 由目前用户拥有 |
-e | 是存在的 |
-R -W -X -O 是前面4种的大写,表示对实际用户 | |
-z | 文件存在,并没有内容(对目录来说永远为假) |
-s | 文件或目录存在,且有内容 |
-f | 是普通文件 |
-d | 是普通目录 |
-l | 是符号链接 |
-C | 最后一次inode变更后到今天的天数 |
-M | 最后一次修改到今天的天数 |
-A | 最后一次访问到尽头的天数 |
虚拟文件句柄
如果我们需要判断一个文件是否是可读可写的,那么我们可能需要使用如下的代码
if ( -r $file and -w $file ) {
# do something
}
但实际上这是个非常不划算的操作,因为在-r的时候,我们就其实已经拿到了文件的信息,第二次没有必要再获取一次。这个时候就可以使用perl独有的虚拟句柄来解决
if (-r $file and -w _ ) {
# do something
}
虚拟文件句柄_
指代的是最近一次文件查询,它可可以不写在一个表达式里
if (-r $file) {};
if (-w _ ) {};
栈式测试
除了使用虚拟句柄以外,perl还提供了栈式测试的方式
if (-r -w -x -o $file) {
# do something
}
值得注意的是,栈式写法应该用于返回的都是boolean的测试符,而不要用于返回数字的。
stat
stat($file)
返回的是一个数组,不同位置代表了不同的信息
localtime
获取当前的时间
目录句柄
如果想要从目录里获得文件列表,那么可以使用目录句柄。
opendir($dir, '/tmp/');
foreach $f (readdir $dir) {
print "one file in /tmp/ is $f\n";
}
closedir($dir);
删除,重命名,软链接
unlink(file1,file2,file3...);
rename "oldname", "newname"; #必须属于一个盘,其他盘的话则不行
symlink "realFile" "linkFile"; # 软链接
10.进程管理
system函数
启动进程最简单的方式就是通过system函数,例如system "date";
、
注意,如果要使用到shell环境的变量,那么一定是要使用单引号,或者转义字符来表示
system 'ls $HOME' or system "ls \$HOME"
exec 函数
与system函数用法基本是一样的,但system函数是在perl空闲的时候去调用子程序,而exec是perl进程自己去执行子程序任务。exec一般会同fork一起使用,在吃不准要使用什么的情况下,system一般总是对的。
写在exec后面的代码是不会被执行的,除非是编程接管启动中的错误
exec "date"
die "date couldn't run: $!\n"
反引号获取输出
有的时候我们想要获取shell的执行结果,而system和exec都是输出到perl的标准输出,那我们就可以考虑使用反引号来获取
my $now = `date`;
print "the time is now $now\n";
奇淫巧技
智能匹配
考虑如下这种场景,我们想在哈希中找出包含Fred的key,并将其key打印出来。对此我们首先会想到的方法是,通过循环来遍历,然后对key判断是否包含,判定结果为true则打印出来。
my $flag = 0;
foreach my $key ( keys %names ) {
next unless $key =~ /Fred/;
$flag = $key;
last;
}
上面写法的好处是,对于任何版本的perl都使用。在5.010版本以上,提供了智能操作符,我们可以将上面的需求写作一行。
say "i found a key matching 'Fred'" if %names ~~ /Fred/;
正则匹配提取
考虑到下面2个代码
#!/usr/bin/perl
my $line = "name: john, age: 18, birthday: 2003.10, height: 188";
printf("old message= $line\n");
if ( $line =~ /name: (\w+), age: (\d+), birthday: (.+), height: (\d+)/) {
printf("1=%s 2=%s 3=%s 4=%s\n", $1,$2,$3,$4);
} else {
printf("nothing\n");
}
if ( $line =~ /name: \w+, age: \d+, birthday: (.+), height: (\d+)/) {
printf("1=%s 2=%s 3=%s 4=%s\n", $1,$2,$3,$4);
} else {
printf("nothing\n");
}
第一个if条件中,所有的匹配像都被圆括号包裹了,第二个if条件,前面2个没有被包裹。我们运行一下看看结果。
old message= name: john, age: 18, birthday: 2003.10, height: 188
1=john 2=18 3=2003.10 4=188
1=2003.10 2=188 3= 4=
我们发现第二个的if可以匹配到内容,但是$3 $4
的内容都是空的,只有$1 $2
有内容,并且内容是有带括号的birthday和height的。所以,perl 正则匹配会将被()
包裹的内容存放到$1 $2 ...
的变量中去,这点在实际编程的时候十分的有用。