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 00:00>

Last updated: <2018-02-09 Fri 22:18>

Creator: Emacs 25.3.1 (Org mode 9.1.4)