Perl

Table of Contents

1. Perl 简介

Perl was originally developed by Larry Wall in 1987 as a general-purpose Unix scripting language to make report processing easier. Since then, it has undergone many changes and revisions. Though Perl is not officially an acronym, there are various backronyms in use, the most well-known being "Practical Extraction and Reporting Language".

参考:
Perl Official Site: https://www.perl.org/
Online Perl Documentation: https://www.perl.org/docs.html
Perl Manual Pages: http://perldoc.perl.org/perl.html

1.1. Perl 历史版本

Table 1: Perl source code releases
  Release Date
Larry 1.000 1987-Dec-18
Larry 2.000 1988-Jun-05
Larry 3.000 1989-Oct-18
Larry 4.000 1991-Mar-21
Larry 5.000 1994-Oct-17
Larry 5.001 1995-Mar-13
Larry 5.002 1996-Feb-29
  5.003 1996-Jun-25
Chip 5.004 1997-May-15
Sarathy 5.005 1998-Jul-22
Sarathy 5.6.0 2000-Mar-22
Jarkko 5.7.0 2000-Sep-02
Jarkko 5.8.0 2002-Jul-18
Hugo 5.9.0 2003-Oct-27
Rafael 5.10.0 2007-Dec-18(注:离上个版本有 4 年!有众多的新特性)
Jesse 5.11.0 2009-Oct-02
Jesse 5.12.0 2010-Apr-12
Leon 5.13.0 2010-Apr-20
Jesse 5.14.0 2011-May-14
David G 5.15.0 2011-Jun-20
Ricardo 5.16.0 2012-May-20
Zefram 5.17.0 2012-May-26
Ricardo 5.18.0 2013-May-18

参考:http://perldoc.perl.org/perlhist.html

2. Perl 帮助文档

Table 2: Perl 常用帮助文档
命令 描述
man perl Perl 帮助文档概览
man perlintro A brief introduction of Perl
man perltoc 所有 Perl 文档的详细目录
man perlcheat 查询备忘单
man perldata 查询数据类型
man perlop 查询操作符和优先级
man perlre 查询正则表达式
man perlfunc 查询内置函数
man perlvar 查询内置变量

2.1. perldoc

perldoc 是一个很好的查看 Perl 帮助文档的工具。

查看模块说明,如:

perldoc File::Basename

查看内置函数说明,如:

perldoc -f split

查看内置变量说明,如:

perldoc -v '$_'

查看模块位置,如:

perldoc -l File::Basename

查看模块详细信息,包括模块源码,如:

perldoc -m File::Basename

3. Perl 语言

3.1. 数据类型

参考:“Perl 语言编程”第 1 章。

3.1.1. 标量($)

my $i = 100;
print $i

3.1.2. 数组(@)

my @abc = ('a', 'b', 'c');

# 数组的第一个元素:
print $abc[0]

# 数组的最后一个元素:
print $abc[$#abc]
# 或者
print $abc[-1]

# $#abc表示数组@abc的最后一个元素的索引值。
print $#abc
# 如这里,$#abc为2(数组索引从0开始)。

3.1.3. 散列(%)

my %xyz = ('a', 100, 'b', 200, 'c', 300);

# 它和下面语句的作用相同:
my %xyz = ();
$xyx{a} = 100;
$xyz{b} = 200;
$xyz{c} = 300;

3.1.4. typeglob

typeglob 可以看做是 perl 的一种数据类型!但由于 perl 中有引用,typeglob 很少会使用到。
像数组用@开头,hash 用%开头一样,typeglob 用*开头。在 man perldata 中搜索 typeglob,可以查看 typeglob 的相关文档。

参考:http://blog.csdn.net/xshalk/article/details/8250935

3.2. my, local, our 的区别

my 声明的变量为词法变量(lexical 变量)。
词法变量的作用域受限于定义它的最内层语句块(或文件)。应该尽量只用词法变量。

local 是把包变量局域化。
注:当变量被局域化后,其值会被重设为 undef。如果要保留以前的值,则可以 local $var=$var,看起来怪,但却有效。
注:对 lexical 变量使用 local 关键字是没有什么意义的,这也是非法的。

our 的作用是声明一个全局变量(这个全局变量可能在其他文件中定义)

参考:
PBP 第五章
http://shake863.iteye.com/blog/189799
http://www.cnblogs.com/softwaretesting/archive/2012/01/13/2321310.html

3.3. q, qq, qx, qr 等的区别

参考: perldoc perlop

           Customary  Generic        Meaning        Interpolates
               ''       q{}          Literal             no
               ""      qq{}          Literal             yes
               ``      qx{}          Command             yes*
                       qw{}         Word list            no
               //       m{}       Pattern match          yes*
                       qr{}          Pattern             yes*
                        s{}{}      Substitution          yes*
                       tr{}{}    Transliteration         no
                        y{}{}    Transliteration         no
               <<EOF                 here-doc            yes*

q{}相当于给整个列表加上单引号
qq{}相当于给整个列表加上双引号
qw{}相当于给列表里的每个单词添加单引号
qx{}相当于执行外部命令
qr{}生成正则表达式

举例说明:
qw(fred barney betty wilma dino)等同于:
('fred', 'barney', 'betty', 'wilma', 'dino')

3.4. 内置变量

Perl 内置变量如表 3 所示。

参考:Perl 语言编程(第三版,何伟平译),第 28 章
参考:http://www.cnblogs.com/ace9/archive/2011/04/29/2032755.html

Table 3: perl 内置变量
PERL 内置变量 说明
$_   默认的输入/输出和格式匹配空间
$"   列表分隔符
$#   打印数字时默认的数字输出格式
$$   Perl 解释器的进程 ID
$&   与上个格式匹配的字符串
$(   当前进程的组 ID
$)   当前进程的有效组 ID
$<   当前执行解释器的用户的真实 ID
$>   当前进程的有效用户 ID
$*   设置 1 表示处理多行格式。现在多以/s 和/m 修饰符取代之
$,   当前输出字段分隔符
$.   上次阅读的文件的当前输入行号
$/   当前输入记录分隔符,默认情况是新行
$\    当前输出记录的分隔符
$:   字符设置,此后的字符串将被分开,以填充连续的字段
$;   在仿真多维数组时使用的分隔符
$?   返回上一个外部命令的状态
$!   根据上下文内容返回错误号或者错误串,其数字值是 errno 的值,其字符串值是对应的系统错误字符串
$@   Perl 解释器从 eval 语句返回的错误消息,如果为空,表示上一次 eval 命令执行成功
$[   数组中第一个元素的索引号,默认为 0
$]   Perl 解释器的子版本号
$%   当前输出页号
$=   当前页面可打印行的数目
$-   当前页可打印的行数,属于 Perl 格式系统的一部分
$~   当前报表输出格式的名称,默认值是文件句柄名
$^   当前报表输出表头格式的名称,默认值是带后缀"_TOP"的文件句柄名
$^A 打印前用于保存格式化数据的变量
$^D 调试标志的值
$^E 在非 UNIX 环境中的操作系统扩展错误信息
$^F 最大的文件捆述符数值
$^H 由编译器激活的语法检查状态
$^I 内置控制编辑器的值
$^L 发送到输出通道的走纸换页符
$^M 备用内存池的大小
$^O 操作系统名
$^P 指定当前调试值的内部变量
$^R 正则表达式块的上次求值结果
$^S 当前解释器状态
$^T 从新世纪开始算起,脚本以秒计算的开始运行的时间
$^W 警告开关的当前值
$^X Perl 二进制可执行代码的名字
$|   控制对当前选择的输出文件句柄的缓冲
$`   在上个格式匹配信息前的字符串
$'   在上个格式匹配信息后的字符串
$+   与上个正则表达式搜索格式匹配的最后一个括号
$-[0]和$+[0] 代表当前匹配的正则表达式在被匹配的字符串中的起始和终止的位置
$0   包含正在执行的脚本的文件名
$ARGV 从默认的文件句柄中读取时(使用"< >"读文件)的当前文件名
%ENV 当前环境变量列表
%INC 通过 do 或 require 包含的文件列表,关键字是文件名,值是这个文件的路径
%SIG 信号列表及其处理方式
@_   传给子程序的参数列表
@ARGV 传给脚本的命令行参数列表
@INC 在导入模块时需要搜索的目录列表

实例:输出记录分隔符$\

#!/usr/bin/env perl

$\ = "\n";       # automatically add newline on print

print "abc";
print "def";

上面程序将输出(省去了在每个 print 后加 \n 来进行换行的麻烦):

abc
def

3.4.1. 查询和修改@INC

3.4.1.1. 查询@INC

如何查询 perl 的模块搜索目录?

$ perl -le "print @INC"
$ perl -V
3.4.1.2. 修改@INC

如何在程序中指定加载路径?

# 方法1:
BEGIN { push @INC, '/my/dir' }

# 方法2:
BEGIN { unshift @INC, '/my/dir' }

# 方法3:
use lib '/my/dir';

注:设置环境变量 PERL5LIB ,可以不用修改程序,而使用非标准库路径下的模块。

3.4.2. 正则表达式特殊变量

Table 4: perl 正则表达式特殊变量
正则表达式特殊变量 说明
$n 包含上次模式匹配的第 n 个子串
$& 前一次成功模式匹配的字符串
$` 前次匹配成功的子串之前的内容
$' 前次匹配成功的子串之后的内容
$+ 前一次使用括号的模式匹配的字符串

正则表达式实例:

use 5.010;

$_='ABCdef123GH';
if (/([a-z]+)(\d+)/) {
  say "\$1 is $1";
  say "\$2 is $2";
  say "\$& is $&";
  say "\$` is $`";
  say "\$' is $'";
} else {
  say "dont match";
}

上面程序将输出:

$1 is def
$2 is 123
$& is def123
$` is ABC
$' is GH

3.4.3. __DATA__和__END__

DATA 是特殊文件句柄,对应的内容是文件末尾__DATA__标记(由于历史的原因,__DATA__标记也可以用__END__标记代替)处开始到文件结束。

详情请参看 perldoc perldata 中 special literals 一节:

The two control characters ^D and ^Z, and the tokens __END__ and __DATA__ may be used to indicate the logical end of the script before the actual end of file.  Any following text is ignored.

Text after __DATA__ may be read via the filehandle "PACKNAME::DATA", where "PACKNAME" is the package that was current when the __DATA__ token was encountered.  The filehandle is left open pointing to the line after __DATA__.  The program should "close DATA" when it is done reading from it.  (Leaving it open leaks filehandles if the module is reloaded for any reason, so it's a safer practice to close it.)  For compatibility with older scripts written before __DATA__ was introduced, __END__ behaves like __DATA__ in the top level script (but not in files loaded with "require" or "do") and leaves the remaining contents of the file accessible via "main::DATA".

文件句柄 DATA 使用实例:

#!/usr/bin/env perl
print <DATA>;

__DATA__
The quick brown fox jumps over the lazy dog

运行上例会输出:The quick brown fox jumps over the lazy dog
把上例中的__DATA__换为__END__,其输出一样。

3.5. 用正则表达式进行匹配和替换 (m//s///)

要进行正则匹配可以用 m/regex/modifiers ,默认匹配 $_
说明:正则匹配中的定界符 // 可以换成其它成对的定界符。 如果采用双斜线作为定界符,则开头的 m 字符可以省略。
例如 /test/, m/test/, m(test), m<test>, m{test}, m[test] 的含义相同,都是尝试匹配 $_ 中的 test 字符。

要进行正则替换可以用 s/regex/replacement/modifiers ,默认操作 $_

参考:
Perl regular expressions quick start: http://perldoc.perl.org/perlrequick.html
Perl regular expressions tutorial: http://perldoc.perl.org/perlretut.html

3.5.1. 绑定操作符 (=~)

默认地,模式匹配(m//)和替换(s///)的操作对象是 $_ ,可以用绑定操作符 =~ 告诉 Perl 我们想要匹配或替换指定的字符串,而不是操作 $_

3.5.2. 获取部分匹配

=~ 放入“列表上下文”中,将返回第个分组,即 $1, $2 等等。

如:

$var1 = 'ABC123def456GH';

# 我们想找到小写字母及后面紧根的数字。
($var2, $var3) = $var1 =~ /([a-z]+)(\d+)/;  # var2
print "var2=$var2\n";  # 输出 var2=def
print "var3=$var3\n";  # 输出 var3=456

# 我们只想找到小写字母后的数字,而不关心小写字母本身是什么。
($var4) = $var1 =~ /(?:[a-z]+)(\d+)/;
print "var4=$var4\n";  # 输出 var4=456

# 也可以直接使用$1
if ($var1 =~ /(?:[a-z]+)(\d+)/) {
  print "$1\n";        # 输出 456
}

参考:http://perldoc.perl.org/perlretut.html#Extracting-matches

3.5.2.1. 匹配取反 (!~)

用绑定操作符 =~ 进行模式匹配(m//)时,能匹配上时返回为真。 如果我们希望不能匹配上时返回为真,则可以使用 !~

if ("Hello World" =~ /World/) {
  print "It matches\n";
} else {
  print "It doesn't match\n";
}

上面的代码和下面代码的功能是一样的。

if ("Hello World" !~ /World/) {
  print "It doesn't match\n";
} else {
  print "It matches\n";
}

3.6. 函数和函数原型

Perl 可用函数原型来限制函数的调用方式。

我们知道,perl 中函数的定义不要指定参数个数及类型,所有传入的参数都保存在@_数组中。

sub fun_test1 {
  print "OK\n";
}

这个函数可以通过 fun_test1(),fun_test1(1,2)等几乎任何方式调用(这样做其实没有什么意义)。

如果想指定参数个数和类型,那么就要使用函数原型!它的作用是限制函数的调用方式。

sub fun_test2() {
  print "OK\n";
}

上面就是函数原型,只能通过 fun_test2()这种方式来调用,用其它方式调用会报编译时的错误。

参考:
perldoc perlsub
https://perldoc.perl.org/perlsub.html
perl 语言编程,第六章 子例程

3.6.1. 参数“引用传递”(通过 $_[0]和$_[1] 等可访问函数参数)

Perl 中,当一个自定义函数接收参数时,它将放在数组 @_ 中,即使用 $_[0] , $_[1] 等可以从该数组中取第 1 个、第 2 个等元素。如果对 $_[0] , $_[1] 进行修改则相应实参也会被修改,这种方式属于参数的“引用传递”(而不是“值传递”)。 说明:“引用传递”中的“引用”和 perl 中的“引用”没有任何关系。

#!/usr/bin/env perl

use strict;
use warnings;

my $a = 10;
my $b = 20;
my $c = 30;

sub do_something {
  $_[0] = 1;
  $_[1] = 2;
  $_[2] = 3;
}

do_something($a,$b,$c);

print "after calling subroutine a = $a, b = $b, c = $c\n";  ## a = 1, b = 2, c = 3

如果你并不想要修改实参,则可以像下面这样做:

sub do_something {
    my ($p1,$p2,$p3) = @_;
    $p1 = 1;
    $p2 = 2;
    $p3 = 3;
}

参考:http://www.perltutorial.org/passing-parameters-to-subroutine/

3.6.2. 函数原型标记的说明

摘自 perldoc perlsub
Any unbackslashed "@" or "%" eats all remaining arguments, and forces list context. An argument represented by "$" forces scalar context. An "&" requires an anonymous subroutine, which, if passed as the first argument, does not require the "sub" keyword or a subsequent comma.
A "*" allows the subroutine to accept a bareword, constant, scalar expression, typeglob, or a reference to a typeglob in that slot.

各个原型标记的说明:

 $ 表示接受一个标量。

 @或% 会吃掉剩下的所有参数,表示接受多个标量。

 & 表示接受一个匿名过程。

 * 表示接受bareword, constant, scalar expression, typeglob

 ; 分开必要参数和可选参数,后面的为可选参数。

 \ 前面提到@可以接受多个标量,怎么限制其接受一个数组呢?用\@即可。
注:在\后面可跟多个标记,放在[]中即可,如sub myref (\[$@%&*]),允许myref的调用方式有:
myref $var
myref @array
myref %hash
myref &sub
myref *glob

 + 有点像\[@%](不太明白)
The "+" prototype is a special alternative to "$" that will act like "\[@%]" when given a literal array or hash variable, but will otherwise force scalar context on the argument.  This is useful for functions which should accept either a literal array or an array reference as the argument.

3.6.3. 函数原型声明和调用方式举例

声明为                    调用方式
sub mylink ($$)          mylink $old, $new
sub myvec ($$$)          myvec $var, $offset, 1
sub myindex ($$;$)       myindex &getstring, "substr"
sub mysyswrite ($$$;$)   mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (@)        myreverse $a, $b, $c
sub myjoin ($@)          myjoin ":", $a, $b, $c
sub mypop (+)            mypop @array
sub mysplice (+$$@)      mysplice @array, 0, 2, @pushme
sub mykeys (+)           mykeys %{$hashref}
sub myopen (*;$)         myopen HANDLE, $name
sub mypipe (**)          mypipe READHANDLE, WRITEHANDLE
sub mygrep (&@)          mygrep { /foo/ } $a, $b, $c
sub myrand (;$)          myrand 42
sub mytime ()            mytime

3.7. 嵌套子函数

perl 中可以用 local 定义嵌套子函数。
注意:一般情况下都不要用嵌套子函数,完全可以用其它方法来实现相同的功能。

3.7.1. 嵌套子函数举例

下例中,wanted 是嵌套在 fun_1 中的子函数。

use File::Find;

fun_1();

sub fun_1{
  my @all_files;
  local *wanted = sub { -f && push @all_files, $File::Find::name;};

  find( \&wanted, "/home/xxx/test");
  foreach my $file (@all_files) {
    print "file: $file\n";
  }
}

如果把子函数 wanted 定义在最外层,则变量@all_files 也要放到最外层。

注意:这里仅仅是举个例子,完全没必要用嵌套子函数。
可以把@all_files 作为参数以引用的形式传递给一个函数,没必要用嵌套子函数。

参考:
http://perldoc.perl.org/perlref.html#Function-Templates
Perl 语言编程(第三版,何伟平译),第八章 引用-使用硬引用-闭包-嵌套子例程

3.8. 引用

3.8.1. 定义引用

3.8.1.1. 方法一:使用斜线\

定义变量的时候,在变量名前面加个\,就得到了这个变量的一个引用,比如:

# 直接量的引用(直接量的引用是只读的)
my $oneref = \1 ;

# 标量的引用
my $scalar=1 ;
my $sref=\$scalar ;

# 数组的引用
my @array= (1,2,3) ;
my $aref=\@array ;
# 对数组中的元素,也一样可以取引用:
my $a_elem_ref = \$array[0];

# 哈希的引用
my %hash= ("name"=>"zdd","age"=>30,"gender"=>"male") ;
my $href=\%hash ;
# 对哈希元素进行取引用:
my $h_elem_ref = \$hash{'name'};

# 子程序的引用
sub subroutine {print "1"}
my $sub_ref=\&subroutine ;
3.8.1.2. 方法二:匿名引用

方法一不是很常用,最常用的还是匿名引用,方法如下:
匿名数组引用——用[]定义
$aref= [ 1,"foo",undef,13 ];
匿名数组的元素仍然可以是匿名数组,所以我们可以用这种方法构造数组的数组,可以构造任意维度的数组。如:

my $aref = [
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9],
];

匿名哈希引用——用{}定义
$href= { APR =>4, AUG =>8 };

3.8.2. 使用引用(解引用)

定义了引用之后,可以使用不同的形式来访问引用(解引用)。

3.8.2.1. 方式一:花括号

这是最基本的方式。
将引用放在代码块(即花括号括起来的区块)中返回,然后把它当作某个变量或子程序的标识符使用。

如:

my $a = 1;
my $s_ref = \$a;
print ${$s_ref};  #相当于print $a;

又如:

my @a = 1 .. 5;
my $a_ref = \@a;
print "@{$a_ref}";  #相当于print "@a";
3.8.2.2. 方式二:省略花括号,多加个$符号

相当于方式一,省略花括号,直接使用标量变量的名字,前面再加上$符号。

my $scalar = 1 ;
my @array = (1, 2, 3) ;
my %hash = ('zdd' => 30, 'autumn' => 27) ;

my $s_ref = \$scalar ;   # scalar reference
my $a_ref = \@array ;    # array reference
my $h_ref = \%hash ;     # hash reference

# 方法二
print $$s_ref, "\n" ;
print @$a_ref, "\n" ;
print %$h_ref, "\n" ;
print $$a_ref[2], "\n" ;
print $$h_ref{'zdd'}, "\n" ;

与普通变量的访问方法相比,假设原来的变量名是 name,其引用为 name_ref,则此方法在 name 出现的地方用$name_ref 代替。

3.8.2.3. 方法三:箭头符号->(不能用于标量,适应于数组、散列和子例程的引用)

对于数组、散列和子例程的引用,有种很简洁的解此用方式,就是使用箭头符号->

my @array = (1, 2, 3) ;
my %hash = ('zdd' => 30, 'autumn' => 27) ;

my $a_ref = \@array ;    # array reference
my $h_ref = \%hash ;     # hash reference

# 方式三,不适用于标量
print $a_ref->[0], "\n" ;
print $h_ref->{'zdd'}, "\n" ;

3.8.3. 测试引用

3.8.3.1. 如何判断一个变量是否是引用?

使用 ref 函数即可,如果变量是引用则返回真,否则返回假。

3.8.3.2. 如何确定引用类型?

实际上,ref 会返回引用对应的类型。
基本的引用类型有如下几种:SCALAR, ARRAY, HASH, CODE, GLOG, Regexp。
注意:正则表达式的引用类型的名称很奇怪,混合了大小写,结尾还有个字母 p。

my $aref1 = [1, 2, 0] ;

print ref $aref1, "\n" ; #输出 ARRAY

if (ref $aref1) {
    print "true\n" ; #输出 true
}
3.8.3.3. 如何判断两个引用是否指向同一个目标?

可以用 eq,这将以字符串的形式判断,也可以使用==

my $aref1 = [1, 2, 0] ;
my $aref2 = $aref1 ;
print $aref1, "\n" ;
print $aref2, "\n" ;

if ($aref1 eq $aref2) {
    print "reference equal\n" ;
}
if($aref1 == $aref2) {
    print "reference equal\n" ;
}

产生如下输出
ARRAY(0x248bec)
ARRAY(0x248bec)
reference equal (eq)
reference equal (==)

3.8.4. 为什么使用引用(可构造复杂的数据结构)

在 perl4 中,hash 表中的 value 字段只能是 scalar,而不能是 list,这对于有些情况是很不方便的,比如有下面的数据:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
我们想要按国家将城市分类,每个国家后面对应城市列表,如果用 perl4 来做,必须将城市列表组合成字符串才行,如果用 perl5 就可以用引用来做,有了引用,就可以构造复杂的数据结构,就可以用列表作为 hash 的值了。

3.8.5. 引用实例

3.8.5.1. 数组的数组
@a = (
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
);
print $a[1][2]
# $a[1][2]表示第二行第三列元素6,也可以写成$a[1]->[2],不过很少有人这么写。还可以写成${$a[1]}[2],几乎没人这么写!

我们知道[1, 2, 3]定义了一个(1, 2, 3)的匿名引用,所以数组 a 实际上包含三个元素,每个元素是一个引用,该引用指向一个数组,所以可以用上面的方法访问数组元素(注意,下标从 0 开始)

多维数组的另一个写法如下:

my $aref = [1, [2, 3], [4, 5, 6]] ;
print $aref->[0] , "\n" ; #输出1
print $aref->[1][1], "\n" ; #输出3
print $aref->[2][0], "\n" ; #输出4

这两者的区别有以下几点:
• 前者是真正的数组,所以定义变量是使用@,后者是指向匿名数组的引用,所以定义的时候使用$
• 前者的数组元素是匿名数组,而外层数组则是实体数组,后者无论元素还是外层数组都是匿名数组
• 前者可以用$a[x][y]的形式访问,而后者只能用解引用的方式访问,即$a->[x][y]的形式。

3.8.5.2. 哈希的哈希

也就是哈希表中的每个元素也是一个哈希表,比如一个学生集合组成的哈希,其 key 是学生名字(唯一),其值是每个学生的属性,比如年龄,身高及学号等。
my $student_properties_of = {
'zdd' => {
'age' => 30,
'hight' => 170,
'id' => '001',
},

'autumn' => {
'age' => 27,
'hight' => 165,
'id' => '002',
}
} ;

3.8.6. 引用的赋值

$aref2 = $aref1;

将使得$aref2 和$aref1 指向同一个数组。
如果想将$aref1 指向的数组拷贝一份给$aref2 的话,使用下面的方法:

$aref2 = [@{$aref1}];

[] 里面对数组进行解引用,而 [] 以解引用后的数组为内容生成了一个新的匿名数组,又赋值给$aref2。
注意: [] 是不可缺少的。

$aref2 = @{$aref1};

由于=左边是标量,所以右边的数组会被解释为标量环境,得到的是数组元素个数,而不是元素本身。但是如果加上[]就可以了,这样 perl 知道这是一个匿名数组的赋值。

4. perl 命令行选项

Usage: perl [switches] [--] [programfile] [arguments]
  -0[octal]         specify record separator (\0, if no argument)
  -a                autosplit mode with -n or -p (splits $_ into @F)
  -C[number/list]   enables the listed Unicode features
  -c                check syntax only (runs BEGIN and CHECK blocks)
  -d[:debugger]     run program under debugger
  -D[number/list]   set debugging flags (argument is a bit mask or alphabets)
  -e program        one line of program (several -e's allowed, omit programfile)
  -E program        like -e, but enables all optional features
  -f                don't do $sitelib/sitecustomize.pl at startup
  -F/pattern/       split() pattern for -a switch (//'s are optional)
  -i[extension]     edit <> files in place (makes backup if extension supplied)
  -Idirectory       specify @INC/#include directory (several -I's allowed)
  -l[octal]         enable line ending processing, specifies line terminator
  -[mM][-]module    execute "use/no module..." before executing program
  -n                assume "while (<>) { ... }" loop around program
  -p                assume loop like -n but print line also, like sed
  -s                enable rudimentary parsing for switches after programfile
  -S                look for programfile using PATH environment variable
  -t                enable tainting warnings
  -T                enable tainting checks
  -u                dump core after parsing program
  -U                allow unsafe operations
  -v                print version, patchlevel and license
  -V[:variable]     print configuration summary (or a single Config.pm variable)
  -w                enable many useful warnings
  -W                enable all warnings
  -x[directory]     ignore text before #!perl line (optionally cd to directory)
  -X                disable all warnings

参考: man perlrun
参考:《Perl 语言编程(第三版,何伟平译)》第 19 章

4.1. -e 'code'选项 (直接执行 code)

用-e 'code'选项可以直接执行 code,简单的代码就不必写入到文件中再执行了。

$ perl -e 'print "hello\n"'

4.2. -n 选项(常用来模拟 sed 或 awk)

-n 选项,使 Perl 认为在你的脚本周围围绕着下面的循环(像 sed -n 或者 awk 中一样):

while(<>) {
   ...   # 你的脚本在这里
}

-n 选项实例:

$ cat file1
Torbin Ulrich   98107
Yeshe Dolma     98117

$ perl -n -e 's/^/      /g; print;' file1
      Torbin Ulrich   98107
      Yeshe Dolma     98117

$ sed 's/^/      /g' file1
      Torbin Ulrich   98107
      Yeshe Dolma     98117

4.3. -p 选项(和-n 类似,可省略 print)

-p 选项和-n 类似,用了-p 后就不需要显示地用 print 函数了。

$ perl -p -e 's/^/      /g;' file1  # -p does the printing.
      Torbin Ulrich   98107
      Yeshe Dolma     98117

4.4. -l 选项(print 后插入“输出记录分隔符”)

-l 选项的说明如下:

-l[octnum]
            enables automatic line-ending processing.  It has two separate
            effects.  First, it automatically chomps $/ (the input record
            separator) when used with -n or -p.  Second, it assigns "$\"
            (the output record separator) to have the value of octnum so
            that any print statements will have that separator added back
            on.  If octnum is omitted, sets "$\" to the current value of $/.

下面例子打印所有库文件的路径:

$ perl -e "print foreach @INC"
/etc/perl/usr/local/lib/perl/5.14.2/usr/local/share/perl/5.14.2/usr/lib/perl5/usr/share/perl5/usr/lib/perl/5.14/usr/share/perl/5.14/usr/local/lib/site_perl.

使用-l 选项(省略了 octnum,默认使用$\,即换行符),可让输出更好看:

$ perl -le "print foreach @INC"
/etc/perl
/usr/local/lib/perl/5.14.2
/usr/local/share/perl/5.14.2
/usr/lib/perl5
/usr/share/perl5
/usr/lib/perl/5.14
/usr/share/perl/5.14
/usr/local/lib/site_perl
.

也可以用命令 perl -e 'print "$_\n" foreach @INC' 得到上面的输出结果。

此外,指定-l 选项的参数,可定制分隔符。如:

$ perl -l072 -e "print foreach @INC"
/etc/perl:/usr/local/lib/perl/5.14.2:/usr/local/share/perl/5.14.2:/usr/lib/perl5:/usr/share/perl5:/usr/lib/perl/5.14:/usr/share/perl/5.14:/usr/local/lib/site_perl:.:

5. 其它

5.1. pod (plain old documentation)

Perl 支持一种叫 pod(plain old documentation)的简单文本标记格式。

pod 是在程序中写文档的一种办法,它以 pod 命令指示字(等号=)开始,以=cut 结束!也就是从第 1 个 pod 命令指示字开始到遇到=cut 指定,其中间部分内容都会被 perl 认为是文档而忽略。

=xxx
这中间的代码,都会被perl认为是pod。
=cut

perl 脚本中的 pod 文档部分可以使用工具 pod2textpod2html 方便地转换为 text,html 等。

参考: man perlpod

5.2. perltidy

perltidy 是个 perl 源码格式化工具。

实例:

$ perltidy -b foo.pl     # 先备份原文件,再格式化代码
$ perltidy -pbp foo.pl   # 按PBP要求格式化代码

5.3. 用 critic 审查代码

工具 Critic 可用来审查代码。
Perl::Critic is an extensible framework for creating and applying coding standards to Perl source code.
Critic 使用的大部分的审查规则来自 Damian Conway 的经典之作——"Perl Best Practices"。

安装:

cpan Perl::Critic

查看帮助:

$ man perlcritic
$ perldoc Perl::Critic

使用实例:
检测所有等级为 5 的问题。

$ perlcritic foo.pl

仅检测所有在 pbp 中描述过的问题。

$ perlcritic --theme pbp -1 foo.pl

参考:
http://www.slideshare.net/joshua.mcadams/extending-perl-critic
http://www.slideshare.net/joshua.mcadams/yapcna-2007-customizing-and-extending-perl-critic
http://www.slideshare.net/joshua.mcadams/an-introduction-to-perl-critic

5.3.1. emacs 中调用 perlcritic

6. Perl Tips

6.1. Perl 调试技巧

-d 选项启动自带的调试器,如:

$ perl -d script.pl

如果在调试器中查看 hash 表?
方法 1:
如果用 p 查看 hash 表,其格式是乱的。若改用 x 查看,则非常清晰:

x %hash

方法 2:
用下面方法查看 hash 更完美,把对应关系清楚地显示出来了:

x \%hash

方法 3:
还可以使用模块 Data::Dumper 查看 hash 表。

6.2. 查找未使用变量

方法 1:
网上有人说 use warnings 能够找出未使用的变量。
但测试后发现 use warnings 基本不能用来发现未使用的变量!

#!/usr/bin/perl -w
use warnings;
$x;

运行时,有下面提示。
Name "main::x" used only once: possible typo at foo1.pl line 4.

但是如果用 my 和 our 声明的变量,use warnings 无法发现只声明而未使用!而好的编程规范是只用 our 和 my 声明变量!所以不能依靠 use warnings 来发现未使用的变量!

http://compgroups.net/comp.lang.perl.misc/finding-unused-variables-in-a-perl-script/341813

方法 2:
Perl::Critic::Policy::Variables::ProhibitUnusedVariables
不过,它也有很多情况无法发现!
赋值过一次,但后面一直没有使用的变量,它无法发现,也就是说只能发现未使用变量中没有初始化的这种情况!

上面所说的限制在下面网站有说明:
http://search.cpan.org/~elliotjs/Perl-Critic-1.112_002/lib/Perl/Critic/Policy/Variables/ProhibitUnusedVariables.pm

方法 3:
B::Xref

6.3. 跨平台文件,小心 chomp

chomp removes the current "input record separator", it is saved in built-in perl variables $/.

在不同的操作系统中,perl 会自动把 $/ 设置为合适的 EOL(End of Line)字符:
Linux 平台: \n
Windows 平台: \r\n
Mac 平台(旧): \r
Mac 平台(新): \n

这样,使用 perl 的 chomp 函数,在平台 A 中操作与平台 A 匹配的文本文件不会有问题,但如果有平台 A 中操作与平台 B 匹配的文本文件,就会出现问题。

如果我们知道所要操作的文件的 EOL,可能通过修改内置变量 $/ 的值使 chomp 得到期待的结果。
如果我们并不能确定所要操作的文件的 EOL,则最好不使用 chomp 函数,用下面正则表达式即可处理 Linux/Windows/Mac 三个平台:

s/(\n|\r)//g

参考:
http://www.perlmonks.org/?node_id=549385
https://en.wikipedia.org/wiki/Newline

6.4. 一次读入整个文件

一次读入整个文件到数组中:

my @lines = <$fd>;

一次读入整个文件到标量中:

my $code = do { local $/ = undef; <$fd>};
# 也可以省写为:
my $code = do { local $/; <$fd>};

说明: $/ 是默认的分隔符。 do {} 代码块的目的是把对 $/ 的修改限制在小范围内,不影响程序后续读取操作的行为。

这和下面代码是等价的,但上面代码更简单:

my $code
while (my $line = <$fd>) {
    $code .= $line;
}

参考:PBP 第十章 I/O

6.5. 函数 quotemeta

先举个例子:

#!/usr/bin/perl

while(<STDIN>)
{
        my $text=quotemeta($_);
        print $text;
        system("touch $text");
}

这个程序的目的是 touch file,这个大家都知道。但是你想过没有,如果你想创建一个文件名为*.txt or a*txt 的 file 时,是不是就会有问题了。因为*在 shell 里有特殊的意义,如果想创建带*号文件就的用反引号"\"将它的特殊意义屏蔽掉,这个时候 quotemeta 就派上作用了。
quotemeta 的作用就是将非字母字符(数字也算)前加反斜线"\"来去掉特殊意义。

如上面的程序,你想创建 a*txt,直接输入就可以了而不必考虑需要将*先转换,函数 quotemeta 已经替你做了,这样是不是方便了许多。

参考:http://blog.chinaunix.net/uid-24549940-id-3191115.html

6.6. 查询某个模块位置

查询所有库文件的路径:

perl -le "print foreach @INC"

查询某个模块的位置:

perl -le "print foreach @INC" |xargs find -L |grep MODULE

当然也可以通过下面命令搞定:

perldoc -l MODULE

通过后缀查找:

find `perl -e 'print "@INC"'` -name "*.pm" -print

查看已经安装了哪些模块,包括版本号:

cpan -a

6.7. 解码 quoted-printable 编码

在 Android 上导入的通讯录为 vcf 格式文件,非 ansii 码的字符用 quoted-printable 编码了,无法直观地在电脑上查看。

#!/bin/sh
# a very crude script to replace quoted-printable encoded strings
# (possibly spanning mulitiple lines) with 8-bit characters

perl -pe 's/(=[a-f\d][a-f\d])=\r?\n$/$1/i' $1 \
| perl -pe 'next unless /(=[a-f\d][a-f\d]){2}/i; s/=([a-f\d][a-f\d])/chr(hex($1))/ieg' \
| perl -pe 's/CHARSET=UTF-8.*?:/CHARSET=UTF-8;ENCODING=8BIT:/;'

# First join multiple qp-encoded lines. Make a minimal check
# so as not to mistakenly process non-qp-encoded lines ending with "=".
# Also take care of both linux newline (\n) and dos/windows newline (\r\n).

# Then do the conversion. Make a minimal check so as not to mistakenly
# process non-qp-encoded strings such as "PHOTO;ENCODING=BASE64;"

# Finally change the encoding string, or add one if there wasn't one.

把上面脚本保存为 qp-8bit,运行下面命令就能看到 vcf 中的中文了。

$ ./qp-8bit contacts.vcf > contacts-8bit.vcf

参考:http://user.frdm.info/ckhung/p/vcf2csv/

6.8. 用 emacs 进行 perl 编程

用 cperl-mode 代替 perl-mode
如果用 emacs 打开 perl 程序,会默认进入 perl-mode。其实还有一个更好用的 cperl-mode,它是也是默认安装的。

使用命令 M-x cperl-mode 可以开启 cperl-mode,如果要在打开 perl 程序时自动开启 cperl-mode,可以在.emacs 文件中加入下面这行:

(defalias 'perl-mode 'cperl-mode)

配置 cperl-mode,在.emacs 中加入下面一行将会显示 Index 菜单。

(add-hook 'cperl-mode-hook 'imenu-add-menubar-index)

参考:http://www.emacswiki.org/cgi-bin/wiki/CPerlMode

Author: cig01

Created: <2012-11-05 Mon>

Last updated: <2018-02-09 Fri>

Creator: Emacs 27.1 (Org mode 9.4)