Perl

  • 大量出于实用主义而设定的默认行为
  • 使用了大量符号和缩写方式, 代码可以表示得很奇异
    这种做法事实上降低了可读性, 但部分Perl用户认为这种写法更好.
  • 以正则表达式和文本处理能力为重心
  • JavaScript明显大量借鉴了Perl的语言特征
  • 虽然出发点是好的(例如减少无谓的键盘输入量),
    但Perl的默认行为太多了, 对于Perl不熟悉的人很难理解Perl代码.
  • 和Python一样, 没有字面量的自动装箱机制.
    这使得命令不能通过REPL环境被用户发现, 需要通过查手册得知, 也限制了编辑器的发挥.
  • 学习成本很高, 教材很差, 语言差异大.
#!usr/bin/perl
use 5.010;
say "Hello World!";

use 5.010; 是为了启用Perl 5.10的say函数.
Perl的次版本号固定为三位数字, 因此必须输入5.010而不是5.10.
也可以用 #!/usr/bin/env perl 作为shebang.

Perl里只有双精度浮点数.

1.25
255.000
255.0
7.25e45
-6.5e24
-12e-24
-1.2E-23
255
61_298_040_283_768 # 用下划线增强可读性, 相同的语法已在JavaScript里引入
0377 # 八进制
0xff # 十六进制
0b11111111 # 二进制
0x1377_0B77
0x50_65_72_7C
2+3
5.1-2.4
3*12
14/2
10.2/0.3
10/3
10%3
2**3
  • == 相等
  • != 不等
  • < 小于
  • > 大于
  • <= 小于或等于
  • >= 大于或等于
# 单引号仅可转义\和'两种字符.
'fred'
'barney'
''
'Dont\'t let an apostrophe end this string prematurely!'
'the last character is a backslash: \\'
'hello
there'
'\n' # 不是换行, 而是两个字符
# 双引号可转义所有字符
"barney"
"hello world\n" # 最后的是换行符
"The last character of this string is a quote mark: \""
"coke\tsprite"
"\x{2668}" # Unicode的代码点(code point)
$alef = chr(0x05D0); # 直接用chr函数转换代码点
$alpha = chr(hex('03B1'));
ord('?')# chr的逆运算
$meal = "brontosaurus steak";
$barney = "fred ate a $meal"; # 标量变量内插, 如果标量变量没被赋值过, 就以空字符串代替
$barney = 'fred ate a ' . $meal; # 等价写法
$barney = "fred ate a ${meal}"; # 用于防止变量名出现歧义的写法
@rocks = ('flintstone', 'slate', 'rubble');
print "quartz @rocks limestone\n"; # 内插数组时, 会以空格间隔开每个元素的值
print "$rocks[1]"; # 内插数组的元素, 下标部分可以是表达式
use utf8; # 开启UTF-8(请确保文件也以UTF-8编码保存)
"hello" . "world" # 字符串连接(与PHP一致)
"fred" x 3 # 将字符串重复3次, 该运算符不满足交换律, 左值一定是被重复的字符串, 右值一定是重复的次数.
"barney" x (4+1)
5 x 4.8 # 左值会自动转换为字符串, 右值会自动取整, 相当于 "5" x 4, 得"5555"
  • eq 相等
  • ne 不等
  • lt 小于
  • gt 大于
  • le 小于或等于
  • ge 大于或等于

Perl没有专门的布尔值类型, 判定根据以下规则:

  • 数据类型为数字时, 0为假, 其他数字为真.
  • 数据类型为字符串时, 空字符串 '''0' (在Perl里与数字0为同一个标量值)为假, 其他字符串为真.
  • 如果不是数字也不是字符串, 则先转换为数字或字符串再判断.
  • 如果是引用, 则undef为假, 其他引用为真.
  • 如果是哈希, 至少有一个键值对为真, 空哈希为假.

需要注意的是, Perl的文档并未规定Perl的布尔真值应该为哪个具体的值.
例如, 数字的真值并未规定为1, 尽管在实现时返回的是1.

  • ! 取反

Perl根据运算符对左值和右值分别进行自动转换.
例如 "12" * "3" 的结果会是36, 因为乘法运算符会将操作数自动转换为数字.

字符串转换为数字时, 非数字的部分会被忽略:
"12fred34" * "3" 的结果为36, 原因在于 12 之后的部分都被忽略.
完全不包含数字的字符串会被转换为0.
字符串转换时不会关注"前置零"的存在, 因此 '0377' 会被转换为十进制的377.

use strict; # 从Perl 5.12开始, 如果指定最低版本号, 则自动隐含此编译指令
# JavaScript的严格模式似乎就是从中继承而来
my $bamm_bamm = 3 # 严格模式下必须先用my声明新的变量, 然后才能使用

Perl的警告信息可以警告程序员存在一些自动类型转换,
程序员通常会希望避免这些自动转换的发生.

#!/usr/bin/perl
use warnings; # 开启内置警告信息(从Perl 5.6开始)
$ perl -w ./my_program # 从命令行开启内置警告信息
#!/usr/bin/perl -w # 用shebang开启内置警告信息
#!/usr/bin/perl
use diagnostics; # 显示详细的诊断信息, 这会降低程序的启动速度, 并且需要消耗更多内容
$ perl -Mdiagnostics ./my_program

存储单个标量值的变量.
以魔符 $(sigil) 开头, 以字母, 数字, 下划线构成标识符, 区分大小写.
在开启utf8编译指令时, 也可以用Unicode字符作为变量名.

$fred = 17;
$barney = 'hello';
$barney .= ' ';
$barney .= 'world';
$fred = $fred + 3;
$fred = $fred * 2;
$fred += 5;
$fred *= 3;
$fred **= 3;

Perl里的数组与标量变量使用两个不同的命名空间,
因此一个变量名可以被重复用于数组和标量变量, 两者是独立运行的.
但出于可维护性的角度不鼓励这么做.

$fred[0] = "yabba";
$fred[1] = "dabba";
$fred[2] = "doo";

任何数字表达式都可以用作数组下标, 如果不是整数, 则会自动舍去小数部分.
未使用的数组元素, 值为undef.

$end = $#rocks; # 数组的最后一个下标
$number_of_rocks = $#rocks + 1; # 数组的大小
$rocks[$#rocks] = 'hard rock';
$rocks[-1] = 'hard rock'; # 等价写法, 负索引值
(1, 2, 3)
(1, 2, 3,) # 末尾的逗号被省略
("fred", 4.5)
() # 空列表
(1..100) # 从1到100的100个整数
(1.7..5.7) # 从1到5的5个整数, 小数部分被舍去
(5..1) # 空列表, 范围操作符仅能从左值到右值自增
(0, 2..6, 10, 12) # (0, 2, 3, 4, 5, 6, 10, 12)
($m..$n)
(0..$#rocks)

Perl的一种列表直接量的简写方式, 可以用空白符替代引号.

qw(fred barney betty wilma dino)
# 等价于 ('fred', 'barney', 'betty', 'wilma', 'dino')
qw(
fred
barney
betty
wilma
dino
)
# 自定义定界符
qw/ fred barney betty wilma dino /
qw# fred barney betty wilma dino #
qw{ fred barney betty wilma dino }
qw! yahoo\! google ask man ! # 将yahoo!作为一个元素, 转义被用作定界符的符号
($fred, $barney, $dino) = ('flintstone', 'rubble', undef);
($fred, $barney) = ($barney, $fred); # 交换
# 不对称情况
($fred, $barney) = ('flintstone', 'rubble', 'slate'); # 多余的元素会被忽略
($wilma, $dino) = ('flintstone'); # $dino的值为undef
# 数组引用
@rocks = ('bedrock', 'slate', 'lava');
@tiny = ();
@giant = 1..1e5;
@stuff = (@giant, undef, @giant); # 列表中的@giant会自动展开, 如果数组是空列表, 则会被忽略.
@copy = @quarry;

几乎就是JavaScript的数组方法.

pop(@array);
pop @array;
push(@array, 0);
push(@array, 1..10);
push @array, 1..10;
shift(@array);
shift @array;
unshift(@array, 0);
unshift(@array, 1..10);
unshift @array, 1..10;
@array = qw( pebbles dino fred barney bety );
@removed = splice @array, 2; # 删掉fred及之后的元素
@array = qw( pebbles dino fred barney bety );
@removed = splice @array, 0, 2; # 删掉pebbles和dino
@array = qw( pebbles dino fred barney bety );
@removed = splice @array, 1, 2, qw(wilma); # 用('wilma')替换dino和fred
@array = qw( pebbles dino fred barney bety );
@removed = splice @array, 1, 0, qw(wilma); # 不删除元素, 只插入元素
@result = reverse 6..10;
@result = sort 97..102;
while (my($index, $value) = each @rocks) {
say "$index: $value";
}
# 面对不同的上下文, Perl会采取不同的默认行为, 这种默认行为很大程度上由语言的作者Larry决定
@backwards = reverse qw/ yabba dabba doo /; # doo, dabba, yabba
$backwards = reverse qw/ yabba dabba doo /; # oodabbadabbay
@wilma = undef # 这将得到(undef)而不是undef
@betty = () # 清空数组
@rocks = qw( talc quartz jade obsidian );
print "How many rocks do you have?\n";
print "I have ", @rocks, " rocks!\n"; # 本想输出数组个数, 却输出了数组
print "I have ", scalar @rocks, " rocks!\n"; # 通过scalar操作符强制以标量上下文形式工作
$family_name{'fred'} = 'flintstone';
$family_name{fred} = 'flintstone'; # 键名左右的引号可省略, 但要注意不被当作表达式
%family_name # 访问整个哈希
%some_hash = (
'foo', 35,
'bar', 12.4
); # 数组收缩为哈希
@any_array = %some_hash; # 哈希展开(unwinding)为数组
# 胖箭头表示法
%last_name = (
'fred' => 'flintstone',
'dino' => undef,
'barney' => 'rubble',
'betty' => 'rubble',
);
# 对于由字母, 数字, 下划线构成且不以数字开头的键, 键名左右的引号可以省略
%last_name = (
fred => 'flintstone',
dino => undef,
barney => 'rubble',
betty => 'rubble',
);
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
my @k = keys %hash;
my @v = values %hash;
my $count = keys %hash;
while (($key, $value) = each %hash) {
print "$key => $value\n";
}
foreach $key (sort keys %hash) {
print "$key => $hash{$key}\n";
}
if (exists $book{'dino'}) {
}
delete $books{'dino'}

未赋值的变量值为undef, 类似于JavaScript的undefined.
当undef被视作数字时, 会把它当作0.
当undef被视作字符串时, 会把它当作空字符串.

使用defined函数判断一个值是否是undef:

$madonna = <STDIN>; # Perl将undef作为EOF使用.
if (defined($madonna)) {
print "The input was $madonna";
} else {
print "No input available!\n";
}
$_ = "yabba dabba doo";
if (/abba/) {
print "It matched!\n";
}
# 使用Unicode属性, 这些属性可以在perluniprops文档中查询得到
if (/\p{Space}/) { # 空白符(共有26个字符带有此属性)
}
if (/\p{Digit}/) { # 数字(共有411个不同的数字字符带有此属性)
}
# 从 Perl 5.6开始, 一些正则表达式里的简写形式代表的字符集比过去要大得多(因为支持了ASCII以外的字符)
# 如需改变这一行为, 可以通过 Perl 4.14 的修饰符 =/a= (写在正则表达式末尾flag的位置)严格要求以ASCII语义展开:
use 5.014;
$_ = 'The HAL-9000 requires authorization to continue.';
if (/HAL-[\d]+/a) {
}
# 绑定操作符(不使用$_变量的匹配方法)
my $some_other = "I dream of betty rubble.";
if ($some_other =~ /\brub/ {
print "Aye, there's the rub.\n";
}
my $what = "larry"
while (<>) {
if (/\A($what)/) { # 内插
print "We saw $what in beginning of $_";
}
}
# 捕获变量(这些变量会存活到下一次成功匹配为止)
$_ = "Hello there, neighbor";
if (/\s(\[a-zA-Z]+),/) {
print "the word was $1\n"; # 捕获到$1
}
# 具名捕获, 具名反向引用
use 5.010;
my $names = 'Fred Flintstone and Wilma Flintstone';
if ($names =~ m/(?<last_name>\w+) and \w+ \g{last_name}/) {
say "I saw $+{last_name}";
}
# 替换
$_ = "green scaly dinosaur";
s/(\w+) (\w+)/$2, $1/;
$_ = "fred flintstone";
if (s/fred/wilma/) {
}
$_ = "I saw Barney with Fred.";
s/(fred|barney)/\U$1/gi; # 转大写
s/(fred|barney)/\L$1/gi; # 转小写
s/(\w+) with (\w+)/\U$2\E with $1/i; # I saw FRED with barney. \E用于界定大小写转换的范围
# 小写形式的 \u, \l 只转换一个字符
# 拆分字符串
my @fields = split /separator/, $string;
# 合成字符串
my $result = join $glue, @pieces;
# 列表上下文的模式匹配
$_ = "Hello there, neighbor!";
my($first, $second, $third) = /(\S+) (\S+), (\S+)/;
print "$second is my $third\n";

/fred/ 实为模式匹配操作符 m/fred/ 的简写, 其关键在于斜杠这一定界符是可以被替换的.
也就是说, 可以存在 m(fred), m<fred>, m{fred} 等形式.

/s 修饰符可以让点号匹配任意字符, 包括原先不能匹配的换行符.

/x 修饰符允许我们在模式里加上空白符, 却不影响语义, 这使得正则表达式的可读性获得提升.

/-?[0-9]+\.?[0-9]*/
/-? [0-9]+ \.? [0-9]*/x
/
-? # 一个可有可无的减号
[0-9]+ # 小数点前必须出现一个或多个数字
\.? # 一个可有可无的小数点
[0-9]* # 小数点后面的数字, 有没有都没关系
/x
$line = <STDIN>;
if ($line eq "\n") {
print "That was just a blank line!\n";
} elsif ($line eq "\t") {
print "That was just a tab line!\n";
} else {
print "That line of input was: $line";
}
unless ($line eq "\n") {
print "That line of input was: $line";
} # 尽管语法上支持else, 但出于语义考虑不建议添加
print "$n is a negative number.\n" if $n < 0;
expression ? if_true_expr : if_false_expr
given ($ARGV[0]) {
when ('Fred') { say 'Name is Fred' }
when (/fred/i) { say 'Name has fred in it' }
when (/\AFred/) { say 'Name starts with Fred' }
default { say "I don't see a Fred" }
}
given ($ARGV[0]) {
# ~~ 智能匹配(由大量关于左右值类型的默认行为决定匹配方式)
when ($_ ~~ 'Fred') { say 'Name is Fred' }
when ($_ ~~ /\AFred/) { say 'Name starts with Fred' }
when ($_ ~~ /fred/i) { say 'Name has fred init' }
default { say "I don't see a Fred" }
}
$count = 0;
while ($count < 10) {
$count += 2;
print "count is now $count\n";
last if $condition1 # last相当于其他语言的break
next if $condition2 # next相当于其他语言的continue
}
$count = 0;
until ($count >= 10) {
$count += 2;
print "count is now $count\n";
}
LINE: while (<>) { # 带标签的循环
foreach (split) {
last LINE if /__END__/; # 带标签的last
}
}
foreach $rock (qw/ bedrock slate lava /) {
print "One rock is $rock.\n";
}
# 循环执行完毕后, 控制变量$rock的值会被自动恢复为循环执行前的值
# 在其他编程语言里, 相同的行为主要是以作用域的形式存在
foreach my $rock (qw/ bedrock slate lava /) { # 词法作用域的控制变量
print "One rock is $rock.\n";
}
foreach (1..10) { # 省略控制变量时, 则默认使用$_作为控制变量
print "I can count to $_!\n";
}

除非函数调用时去掉括号会改变表达式的意义, 否则括号可以省略.
函数调用和列表表达式都使用括号作为标识符, 因此推荐在函数/子程序调用后加上括号, 以免被Perl错误理解.

sub marine {
$n += 1; # 所有子程序中的变量都是全局变量
print "Hello, sailor number $n!\n"; # 返回值
}
&marine; # 调用(calling)子程序
sub marine {
state $n = 0; # 在Perl 5.10引入的持久性私有变量(等同于其他语言的静态变量static)
$n += 1;
print "Hello, sailor number $n!\n";
}
sub sum_of_fred_and_barney {
print "Hey, you called me";
$fred + $barney; # 最后一条语句的结果被视作返回值, 也可以用return手动返回(主要用于提前返回)
}
sub max {
# 第一个参数为$_[0], 第二个参数为$_[1]
if ($_[0] > $_[1]) {
$_[0];
} else {
$_[1];
}
}
$n = &max(10, 15);
sub max {
my($m, $n); # 用my操作符创造词法变量(私有变量)
($m, $n) = @_; # 获得子程序的参数, 可以和上一行一起缩写成my($m, $n) = @_;
if ($m > $n) {
$m;
} else {
$n;
}
}
sub max {
my($max_so_far) = shift @_; # 推出第一个参数, 如果参数列表是空的, 则会返回undef, 仍然符合预期行为
foreach (@_) { # 遍历剩余的参数列表
if ($_ > $max_so_far) { # 比较$max_so_far和当前的第一个参数
$max_so_far = $_;
}
}
$max_so_far;
}
  • 在调用子程序之前编译器看到过子程序的定义(内置函数的工作原理)
  • Perl判定该语法只能是一个子程序
my @cards = shuffle(@deck_of_cards); # 省略了shuffle前的&

如果子程序名与内置函数的名称相同, 省略&时调用的将是内置函数.
推荐在自定义的子程序调用前加上&, 用以区别内置函数.

文件句柄建议用大写字母命名.

# 打开文件句柄用的描述符与Unix Shell一致
open CONFIG, 'dino';
open CONFIG, '<dino';
open BEDROCK, '>fred';
open LOG, '>>logfile';
# Perl 5.6以后的三参数写法, 内插字符串时更安全:
open CONFIG, '<', 'dino';
open BEDROCK, '>', $file_name;
open LOG, '>>', &logfile_name();
open CONFIG, '<:encoding(UTF-8)', 'dino'; # 指定编码读
open BEDROCK, '>:encoding(UTF-8)', $filename_name; # 指定编码写, 会确认编码是否正确
open BEDROCK, '>:utf8', $file_name; # 简写, 不会确认编码是否正确, 不推荐
open BEDROCK, '>:crlf', $file_name; # 以CR-LF换行写入
open BEDROCK, '<:crlf', $file_name; # 以CR-LF换行读入
my $success = open LOG, '>>', 'logfile';
if (!$success) {
# 操作失败
}
if (!open LOG, '>>', 'logfile') {
die "Cannot create logfile: $!"; # 终止程序运行, $!是操作系统提供的错误信息
# warn "Cannot create logfile: $!"; # 输出警告
}
# Perl 5.10加入了autodie编译指令, 会在open失败时自动die.
use autodie;
close BEDROCK; # 关闭句柄, 此外Perl具有在重复打开同一个文件时自动关闭上次打开的句柄的默认行为
print LOG "Captain's log, stardate 3.14159\n"; # 打印到文件句柄
select BEDROCK; # 改变默认输出
print "I hope Mr. Slate doesn't find out about this.\n";
print "Wilma!\n";
select STDOUT; # 设置回默认值
$| = 1 # 将缓冲区设置为1(在输出后立即刷新缓冲区)
# 打印Perl能理解的所有字符编码
$ perl -MEncode -le "print for Encode->encodings(':all')"
  • STDIN
  • STDOUT
  • STDERR
  • DATA
  • ARGV
  • ARGVOUT
use File::Basename;
use File::Basename qw/ basename /; # 部分导入
use File::Spec;
my $new_name = File::Spec->catfile($dirname, $basename);