理解Perl引用
2013-07-24 16:17:24 阿炯

本站赞助商链接,请多关照。 Perl引用出现的最大原因就是我们需要更加复杂的数据结构,因为我们的程序面对的环境是复杂。而我们的工作就是创建和使用这两个主要任务。

一、声明,调用
Perl的引用相当于C语言中的指针,有以下几种类型:

1、一般的声明方式
$scalarref=\$foo; #标量引用 SCALAR,$foo='hello';
$constref=\186_282.42; #标量引用 SCALAR
$arrayref=\@arr; #数组引用 ARRAY,@arr=1..100;
$hashref=\%hash; #hash引用 HASH,%hash=('a'=>1,'b'=>2);
$coderef=\&add; #子过程引用 CODE,sub add{($a,$b)=@_;  return $a+$b;}
$globref=\*STDOUT; #GLOB句柄引用
 
使用方式(以下的数组下标,键值假设都存在):
$$scalarref;
$$constref;
$$arrayref [0]; 或 $arrayref->[0];
$$hashref{'a'}; 或 $hashref->{'a'};
&$coderef(1,2); 或 $coderef->(1,2);

2、匿名的方式
另外声明方式采用匿名方式声明 如下(针对数组,hash,子过程)
$arrRef=[1,2,3,4,5]; #指向匿名数组的引用
$hashRef={'a'=>1,'b'=>2}; #指向匿名hash的引用
$subRef=sub{return 1;}; #指向匿名子过程的引用
使用方式是一样的
 
3、符号表引用的声明方式
$scalarref=*foo{SCALAR}; # 和 \$foo 一样
$arrayref=*ARGV{ARRAY}; # 和 \@ARGV 一样
$hashref=*ENV{HASH}; # 和 \%ENV 一样
$coderef=*handler{CODE}; # 和 \&handler 一样
$globref=*foo{GLOB}; # 和 \*foo 一样
$ioref =*STDIN{IO}; # ?...


创建引用不外乎两种方法

方法1(\方法)
$aref=\@array; #$aref保存着指向@array的引用
$href=\@hash; #$href保存着指向%hash的引用

方法2(括号方法)
[ITEMS] 创建一个新的匿名数组,并返回一个数组的引用
{ITEMS} 创建一个新的匿名哈希,并返回一个哈希的引用

$aref=[ 1, "foo", undef, 13 ]; # $aref 保存了这个数组的'引用'
$href={ APR=>; 4, AUG=>; 8 }; # $href 保存了这个哈希的'引用'

区别
#这里:$aref=[1, 2, 3 ];和后面一样:@array=(1, 2, 3);$aref=\@array;

前面一种方法是后面两行的缩写,除了第一种方法没有创建一个多余的数组变量@array。

如果只是编写符号[],你将得到一个新的、空匿名数组。如果你使用符号{},就能得到一个新的、空匿名哈希。

二、解引用
声明一个引用一般是在变量名前加个'\',而解引用是在变量名前加'$'。如:
$s='hello';
$sRef=\$s;
print $$sRef; #输出 hello
 
理解了这点,看下面的
$refrefref=\\\"howdy";
print $$$$refrefref; #输出 howdy

使用引用有如下一些方法
方法1(简单同,不容易错反正给原来用数组或哈希的地方替换就好了,在输出时也可以),始终使用一个有大括号的数组’引用’,来替换一个数组的名字,如@{$aref}代替@array。

数组
对数组的操作----对引用操作
 @a----@{$aref}----数组操作
 reverse @a----reverse @{$aref}----对一个数组做倒序排序
 $a[3]----${$aref}[3]----数组中的一个成员
 $a[3]=17; ${$aref}[3]=17----对一个成员赋值

哈希的’引用’和数组的’引用’完全一样
对h的操作----对引用的操作
 %h----%{$href}----哈希操作
 keys %h----keys %{$href}----从哈希中将键取出来
 $h{‘red’}----${$href}{‘red’}----哈希中的一个成员
 $h{‘red’}=17----${$href}{‘red’}=17----对一个成员赋值

方法2(上面的方法好用,但不方便读,下面的可读性更好些)
${$aref}[3] 可以写成 $aref->[3]. #注$aref->[3]不等同$aref[3],前面的$aref->表示的其实是@aref,后面只是一个标量
${$href}{red}可以写成 $href->{red}. #同上

箭头符号规则:在两个下标之间的箭头是可选的,$a[1][2]来代替$a[1]->[2]
使用方法1中,当大括号里面是一个象$aref这样的标量变量时,可以省略掉这个大括号。例如:@$aref 和@{$aref}是一样。

perl中容易出问题和要注意的地方
下面的操作不会copy‘引用’指向的数组:
$aref2=$aref1;

你将得到两个’引用’,它们都指向同一个数组;如果你修改了$aref1->[23]的值,那么你查看变量$aref2->[23]时,它也相应地变了。所以如果你要copy这个数组,你需要这样:
$aref2=[@{$aref1}];

使用符号 [...] 来创建一个新的匿名数组,而且这个新的数组的’引用’被赋值给了$aref2,这个新的数组用’引用’$aref1所指向的数组的内容来初始化。同样的,要copy一个匿名哈希,你需要这样:
$href2={%{$href1}};
 
@$pointer来访问数组中的元素,形式@$pointer的意义为“取出$pointer中的地址值当作数组使用”。

解引用示例(Dereferencing)

1.scalar
my $scalar = "This is a scalar";
my $scalar_ref = \$scalar;
print "Reference: " . $scalar_ref . "\n";
print "Dereferenced: " . $$scalar_ref . "\n";
print "Dereferenced: " . ${$scalar_ref} . "\n";
---------------
Reference: SCALAR(0x1df87d0)
Dereferenced: This is a scalar

2.array
my $array_ref = ['apple', 'banana', 'orange'];
my @array = @$array_ref;
print "Reference: $array_ref\n";
print "Dereferenced: @array\n";
print "My first fruit is: " . $array_ref->[0] . "\n";

---------------
Reference: ARRAY(0x1d17f30)
Dereferenced: apple banana orange
My first fruit is: apple

3.hash
my $hash_ref = {name => 'Freeoa', age => 23};
my %hash = %$hash_ref;
print "Reference: $hash_ref\n";
print "Dereferenced:\n";
foreach my $k (keys %hash) {
    print "$k: $hash{$k}\n";
}
foreach my $k (keys %$hash_ref) {
    print "$k: " . $hash_ref->{$k} . "\n";
}

---------------
Reference: HASH(0xef2f30)
Dereferenced:
age: 23
name: Freeoa
age: 23
name: Freeoa

4.subroutine
my $sub_ref = sub { print "In a subroutine\n" };
&$sub_ref()

hash with sub
my %hash = (frogs => sub {print "Frogs Jumped...\n"});
&{$hash{frogs}}();
$hash{frogs}->();

---------------
Frogs Jumped...
Frogs Jumped...

三、相关应用
引用的赋值
$aref2=$aref1; 将使得$aref2和$aref1指向同一个数组,如果想将$aref1指向的数组拷贝一份给$aref2的话,使用下面的方法,[]里面对数组进行解引用,而[]以解引用后的数组为内容生成了一个新的匿名数组,又赋值给$aref2。
$aref2=[@{$aref1}];

注意:不能使用下面的形式,外层的[]是不可缺少的。由于=左边是标量,所以右边的数组会被解释为标量环境,得到的是数组元素个数,而不是元素本身。但是如果加上[]就可以了,这样perl知道这是一个匿名数组的赋值。
$aref2=@{$aref1};

判断一个变量是否是引用
使用ref函数即可,如果变量是引用则返回真,否则返回假。实际上它更智能,它会返回引用对应的类型,比如HASH或者ARRAY。
my $aref1=[1, 2, 0];
print ref $aref1, "\n"; #输出 ARRAY
if (ref $aref1){
 print "true\n"; #输出 true
}

判断两个引用是否指向同一个目标
可以用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";
}

通过引用解决列表无法嵌套的问题:
my @array1=(10, 20, 30, 40, 50);
my @array2=( 1, 2, \@array1, 3, 4);
因为\@array1本质上只是一个标量,所以列表不会被扁平化,依旧保留了嵌套层次。

对匿名列表的引用:将列表的()替换为[]:my $array_r=[1, 2, 3, 4, 5];
对匿名哈希的引用:将哈希的()替换为{}:my $hash_r={ apple=> "pomme", pear=> "poire" };
对引用变量解引用通过{$var_r}实现
列表
my @array=(1, 2, 3, 4, 5);
my $array_r=\@array;
my @array2=@{$array_r}; #拷贝了数组

哈希与列表类似
对于数组引用,可以将${$ref}简记为$ref->,例如可以将${$ref}[2]简记为$ref->[2],而将${${ref}[2]}[1]简记为$ref->[2]->[1],并进一步简记为$ref->[2][1]

使用undef销毁一个引用:undef $ref; perl对于引用指向的数据维护一个引用计数,当计数减到0时引用指向的数据被销毁,内存空间被释放。使用引用使得表示复杂的数据结构成为可能,这些数据结构包括矩阵(多维数组)、链表、树、图等。

关于ref(EXPR)函数

如果EXPR是引用则返回对应的类型,否则返回空字符串。如果未指定EXPR,则将使用$_,返回的值取决于所引用的对象的类型。内置类型有:
SCALAR
ARRAY
HASH
CODE
REF
GLOB
LVALUE
FORMAT
IO
VSTRING
Regexp


Perl引用分为直接引用和符号引用,每种Perl引用都有各自的特点和作用,这里向大家简单介绍一下Perl直接应用的概念,希望对大家学习有所帮助。上面的判断方式中,是将HASH字符串硬编码到代码中的。如果不想硬编码,可以让ref对空hash、空数组等进行检测,然后对比:
ref [] # 返回ARRAY
ref {} # 返回HASH

之所以可以对它们进行检测,是因为它们是匿名数组、匿名hash引用,只不过构造出的这些匿名引用里没有元素而已。也就是说,它们是空的匿名引用。除此之外,Scalar::Util模块提供的reftype函数也用来检测类型,它还适用于对象相关的检测。


对数组的Perl引用主要用在解决向函数传递若干个数组的问题。在Perl中,如果向函数传递若干个数组,则他们会将这些数组展开到@_数组中,并不能通过@_这个数组来区分传递过来的参数。如:
sub add{
 my(@ref1,@ref2)=@_;
 print(“ref1:@ref1\n”);
 print(“ref2:@ref2\n”);
}
@a=(1,2,3);
@b=(1,2,3);
@ret=add(@a,@b);
实际输出的是:
ref1:123123
ref2:

可以看到,在子函数add中并没有区分传递过来的两个参数。那么如何向函数传递多个数组或哈希表呢?解决的办法是使用数组Perl引用,如下这个例子说明了如何实现:
sub add{ my @result;
 my($ref1,$ref2)=@_;
 while( @$ref1 && @$ref2 ){
  unshift @result, pop(@{$ref1})+pop(@{$ref2});}
  return @result;}
 @a=(1,2,3);
 @b=(1,2,3);
 @ret=add(\@a,\@b);
 print "@ret\n";

输出:246

列表的Perl引用
创建表的Perl引用将会把表中的最后一个值作为产生的标量:
$reflist=\($a,$b,$c);
print $$reflist."\n"; #输出$c的值。
$reflist=\(1,2...30,40);
print $$reflist."\n"#输出40
 
匿名数组的Perl引用
$arrayreference=[1,2,3]; print $$arrayreference[0]; #输出1
print $arrayreference->[0]; #输出1可以用箭头解Perl引用  

当用pop从数组中取值时,数组的第一个元素会被弹出,但是如果用pop用于匿名数组时,可以实现取值而不影响原有数组,如:
@a=(1,2,3);
$s=pop@{[@a]};#perl作为快来计算@{},而快在计算时将创建对匿名数组的Perl引用。
print “@a\n”;#输出123

当反Perl引用数组Perl引用时,该结果将插入到字符串中,如:想实现输出uc函数的返回结果:
print "uc(abc)\n"; #输出uc(abc),并不能将abc转换成大写
print "@{[uc(abc)]}\n"; #输出ABC,通过数组Perl引用实现大写转换
 
可以通过$#$的方式取匿名数组的长度:
$a=[1,2,3,4];
print "$#$a\n"; #输出3(最后一个元素的下标)
 
用匿名哈希表模仿用户自定义数据类型
sub Point{
 ($x,$y)=@_;
 return{
  x=>$x,
  y=>$y
 };
}

当使用Point类型时,可以这样做:
$point=Point(10,20);
print "x:$point->{x},y:$point->{y}\n";

关于这种用法,请参考:初识Perl匿名函数

四、文件句柄引用

经常使用大写字母的句柄方式(即所谓的裸字,bareword filehandle),现在可以考虑转向使用变量文件句柄的形式,因为只有使用变量句柄的方式,才能创建文件句柄引用。
open DATA,">>","/tmp/a.log" or die "can't open file: $!";
open my $data_fh ,">>","/tmp/a.log" or die "can't open file: $!";
open my $fh, '<', 'castaways.log' or die "Could not open castaways.log: $!";

裸字文件句柄和变量文件句柄用法是完全一致的,能用裸字文件句柄的地方都可以替换为变量文件句柄:
while( <DATA> ) { ... }
while( <$log_fh> ) { ... }

不管使用裸字还是变量文件句柄的方式,在退出文件句柄所在作用域的时候,都会自动关闭文件句柄,无需手动close。不过需要注意的是,使用变量文件句柄的方式,在say/print输出的时候,指定文件句柄时需要使用大括号包围,以免产生歧义:
print {$data_fh} "your output content";

如果想要让某个函数指定输出的文件句柄,也简单,只需将文件句柄作为一个参数即可:
log_message( $log_fh, 'My name is Mr. Ed' );
sub log_message {
    my $fh = shift;
    print $fh @_, "\n";
}

字符串句柄

除了可以将句柄关联到文件(open)、管道、套接字、目录(opendir),还可以将句柄关联到字符串。也就是将一个变量作为文件句柄的关联对象,从这个变量读或从这个变量写。例如:
open my $string_fh, '>>', \my $string;
open my $string_fh, '<', \$multiline_string;

上面第一句声明了一个词法变量$string(初始化为Undef),同时创建了一个文件句柄$string_fh,这个文件句柄的输出对象是词法变量$string指向的数据对象。第二句则是从字符串$multiline_string中读取数据。


现在可以向这个文件句柄中输出一些数据,它们会存储到$string中:
#!/usr/bin/perl
open my $string_fh, ">>",\my $string or die "...$!";

print {$string_fh} "first line\n";
print {$string_fh} "second line";

print $string,"\n"; # 输出两行:first line和second line

如果想将流向标准输出STDOUT默认设备(终端屏幕)的内容改输出到字符串中,需要小心一些,因为STDOUT毕竟是标准输出,程序的很多部分可能都需要使用它。所以尽量在一小片范围内修改标准输出的目标。例如使用大括号包围,并将STDOUT进行local化(裸字文件句柄只能用local修饰):

print "1. This goes to the real standard output\n";
my $string;
{
    local *STDOUT;
    open STDOUT, '>', \ $string;
    print "2. This goes to the string\n";
    $some_obj->noisy_method(); # this STDOUT goes to $string too
}
print "3. This goes to the real standard output\n";

文件句柄容器

说法有点高大上,其实就是将文件句柄存储到数据结构中(例如hash、数组),做一个装文件句柄的容器。

例如有一个文件fa.txt,内容如下。现在想将每一行第二列、第三列存储到以第一列命名的变量中。
freeoa big 1250
net small 910
freeoa big 1380
net small 450
tuner middle 1218


如下:
use v5.10;
while(<>){
    state $fhs; # 定义一个hash引用变量
    my($source, $destination, $bytes) = split;
    unless($fhs->{$source}){# 当hash键(第一列)不存在时,创建字符串句柄
        open my $fh, '>>', $source or die '...';
        $fhs->{$source} = $fh;
    }
    say {$fhs->{$source}} "$destination,$bytes";
}


五、正则表达式引用

这是对Perl正则的一点扩展,主要内容是使用qr//创建正则对象,以及一些其它的技巧。

qr//创建正则对象

因为可以在正则模式中使用变量替换,所以可以将正则中的一部分表达式事先保存在变量中。例如:
$str="hello worlds gaoxiaofang";
$pattern="w.*d";
$str =~ /$pattern/;
print "$&\n";

但这样缺陷很大,在保存正则表达式的变量中存放的特殊字符要防止有特殊意义。例如当使用m//的方式做匹配分隔符时,不能在变量中保存/,除非转义。

perl提供了qr/pattern/的功能,它把pattern部分构建成一个正则表达式对象,然后就可以:
1.在正则表达式中直接引用这个对象
2.可以将这个对象保存到变量中,通过引用变量的方式来引用这个已保存好的正则对象
3.将引用变量插入到其它模式中构建更复杂的正则表达式

其中:
1.qr//的定界符斜线可以替换为其它符号,例如对称的括号类qr() qr{} qr<> qr[],一致的符号类qr%% qr## qr!! qr$$ qr"" qr''等。

2.但是使用单引号作为定界符时比较特殊(即qr'pattern'),它会将pattern部分使用单引号的方式去解析,例如变量$var无法替换,而是表示4个字符。但是正则表达式的元字符仍然起作用,例如$仍然表示行尾。

$str="hello worlds gaoxiaofang";

# 直接作为正则表达式
$str =~ qr/w.*d/;
print "$&\n";

# 保存为变量,再作为正则表达式
$pattern=qr/w.*d/;
$str =~ $pattern; # (1)
$str =~ /$pattern/; # (2)
print "$&\n";

# 保存为变量,作为正则表达式的一部分
$pattern=qr/w.*d/;
$str =~ /hel.* $pattern/;
print "$&\n";

还允许为这个正则对象设置修饰符,比如忽略大小写的匹配修饰符为i,这样在真正匹配的时候,就只有这一部分正则对象会忽略大小写,其余部分仍然区分大小写。

$str="HELLO wORLDs gaoxiaofang";
$pattern=qr/w.*d/i; # 忽略大小写
$str =~ /HEL.* $pattern/; # 匹配成功,$pattern部分忽略大小写
$str =~ /hel.* $pattern/; # 匹配失败
$str =~ /hel.* $pattern/i; # 匹配成功,所有都忽略大小写

qr如何构建正则对象

输出qr构建的正则引用,看看是怎样的结构:
$patt1=qr/w.*d/;
print "$patt1\n";

$patt2=qr/w.*d/i; # 加上修饰符i
print "$patt2\n";

$patt3=qr/w.*d/img; # 加上修饰符img
print "$patt3\n";

上面的print将输出如下结果:
(?^:w.*d)
(?^i:w.*d)
(?^mi:w.*d)

qr的作用实际上就是在我们给定的正则pattern基础上加上(?^:)并带上一些修饰符,得到的结果总是(?^FLAGS:pattern)。

但是上面patt3的修饰符g不见了。先可以看看(?^:)的作用:非捕获分组,并重置修饰符。重置为哪些修饰符?对于(?^FLAGS:)来说,只有这些修饰符"alupimsx"是可用的,即(?^alupimsx:):
1.如果给定的修饰符不在这些修饰符内,则不被识别,有时候会报错
2.如果给定的修饰符属于这几个修饰符,那么没有给定的修饰符部分将采用默认值(不同版本可能默认是否开启的值不同)

所以上面的g会被丢弃,甚至在进一步操作这个正则引用时,会报错。

既然qr给pattern部分加上了(?^:),那么当它们插入到其它正则中的时候,就能保证这一段是独立的,不受全局修饰符影响的模式。

$patt1=qr/w.*d/im;
$patt2=qr/hel.*d $patt1/i;
print "$patt2\n"; # 输出:(?^i:hel.*d (?^mi:w.*d))

正则引用作为标量的用法

既然qr//创建的正则对象引用是一个标量,那么标量可以出现的地方,正则引用就可以出现。例如,放进hash结构,数组结构。

比如放进数组中形成一个正则表达式列表,然后给定一个待匹配目标,依次用列表中的这些模式去匹配。
use v5.10.1;
my @patterns = (
    qr/(?:Willie )?Gilligan/,
    qr/Mary Ann/,
    qr/Ginger/,
    qr/(?:The )?Professor/,
    qr/Skipper/,
    qr/Mrs?. Howell/,
);

my $name = 'Ginger';
foreach my $pattern (@patterns) {
    if( $name =~ /$pattern/ ) {
        say "Match!";
        print "$pattern";
        last;
    }
}

还可以将这些正则引用放进hash中,为每个pattern都使用key来标识一下,例如pattern1是用来匹配什么的:
use v5.10.1;
my %patterns = (
    Gilligan => qr/(?:Willie )?Gilligan/,
    'Mary Ann' => qr/Mary Ann/,
    Ginger => qr/Ginger/,
    Professor => qr/(?:The )?Professor/,
    Skipper => qr/Skipper/,
    'A Howell' => qr/Mrs?. Howell/,
);
my $name = 'Ginger';
my($match) = grep { $name =~ $patterns{$_} } keys %patterns;
say "Matched $match" if $match;

上面将grep语句的结果赋值给了一个标量,所以如果有多个Pattern能匹配$name,多次执行,$match的值将可能会不一样。

构建复杂的正则表达式

有了qr,就可以将正则表达式细化成一小片一小片,然后组合起来。例如:
my $howells = qr/Thurston|Mrs/;
my $tagalongs = qr/Ginger|Mary Ann/;
my $passengers = qr/$howells|$tagalongs/;
my $crew = qr/Gilligan|Skipper/;
my $everyone = qr/$crew|$passengers/;

就像RFC 1738中对URL各个部分的解剖,如果转换成Perl正则,大概是这样的(了解即可):
# 可复用的基本符号类
my $alpha = qr/[a-z]/;
my $digit = qr/\d/;
my $alphadigit = qr/(?i:$alpha|$digit)/;
my $safe = qr/[\$_.+-]/;
my $extra = qr/[!*'\(\),]/;
my $national = qr/[{}|\\^~\[\]`]/;
my $reserved = qr|[;/?:@&=]|;
my $hex = qr/(?i:$digit|[A-F])/;
my $escape = qr/%$hex$hex/;
my $unreserved = qr/$alpha|$digit|$safe|$extra/;
my $uchar = qr/$unreserved|$escape/;
my $xchar = qr/$unreserved|$reserved|$escape/;
my $ucharplus = qr/(?:$uchar|[;?&=])*/;
my $digits = qr/(?:$digit){1,}/;

# 可复用的URL组成元素
my $hsegment = $ucharplus;
my $hpath = qr|$hsegment(?:/$hsegment)*|;
my $search = $ucharplus;
my $scheme = qr|(?i:https?://)|;
my $port = qr/$digits/;
my $password = $ucharplus;
my $user = $ucharplus;

my $toplevel = qr/$alpha|$alpha(?:$alphadigit|-)*$alphadigit/;
my $domainlabel = qr/$alphadigit|$alphadigit(?:$alphadigit|-)*$alphadigit/x;
my $hostname = qr/(?:$domainlabel\.)*$toplevel/;
my $hostnumber = qr/$digits\.$digits\.$digits\.$digits/;
my $host = qr/$hostname|$hostnumber/;
my $hostport = qr/$host(?::$port)?/;
my $login = qr/(?:$user(?::$password)\@)?/;

my $urlpath = qr/(?:(?:$xchar)*)/;

然后可以用上面看上去无比复杂的正则表达式去匹配一个路径是否是合格的http url:
use v5.10.1;
my $httpurl = qr|$scheme$hostport(?:/$hpath(?:\?$search)?)?|;
while( <> ) {
    say if /$httpurl/;
}

正则表达式模块

上面构建的正则太复杂了,很多常用的正则表达式别人已经造好了轮子,直接拿来用就行了。例如Regexp::Common模块,提供了很多种已经构建好的正则表达式。首先安装这个模块:
cpan -i Regexp::Common

以下是CPAN上提供的Regexp::Common已造好的轮子。
Regexp::Common - Provide commonly requested regular expressions
Regexp::Common::CC - provide patterns for credit card numbers.
Regexp::Common::SEN - provide regexes for Social-Economical Numbers.
Regexp::Common::URI - provide patterns for URIs.
Regexp::Common::URI::RFC1035 - Definitions from RFC1035;
Regexp::Common::URI::RFC1738 - Definitions from RFC1738;
Regexp::Common::URI::RFC1808 - Definitions from RFC1808;
Regexp::Common::URI::RFC2384 - Definitions from RFC2384;
Regexp::Common::URI::RFC2396 - Definitions from RFC2396;
Regexp::Common::URI::RFC2806 - Definitions from RFC2806;
Regexp::Common::URI::fax - Returns a pattern for fax URIs.
Regexp::Common::URI::file - Returns a pattern for file URIs.
Regexp::Common::URI::ftp - Returns a pattern for FTP URIs.
Regexp::Common::URI::gopher - Returns a pattern for gopher URIs.
Regexp::Common::URI::http - Returns a pattern for HTTP URIs.
Regexp::Common::URI::news - Returns a pattern for file URIs.
Regexp::Common::URI::pop - Returns a pattern for POP URIs.
Regexp::Common::URI::prospero - Returns a pattern for prospero URIs.
Regexp::Common::URI::tel - Returns a pattern for telephone URIs.
Regexp::Common::URI::telnet - Returns a pattern for telnet URIs.
Regexp::Common::URI::tv - Returns a pattern for tv URIs.
Regexp::Common::URI::wais - Returns a pattern for WAIS URIs.
Regexp::Common::_support - Support functions for Regexp::Common.
Regexp::Common::balanced - provide regexes for strings with balanced parenthesized delimiters or arbitrary delimiters.
Regexp::Common::comment - provide regexes for comments.
Regexp::Common::delimited - provides a regex for delimited strings
Regexp::Common::lingua - provide regexes for language related stuff.
Regexp::Common::list - provide regexes for lists
Regexp::Common::net - provide regexes for IPv4, IPv6, and MAC addresses.
Regexp::Common::number - provide regexes for numbers
Regexp::Common::profanity - provide regexes for profanity
Regexp::Common::whitespace - provides a regex for leading or trailing whitescape
Regexp::Common::zip - provide regexes for postal codes.

这些正则表达式是通过hash进行嵌套的,hash的名称为%RE。例如模块Regexp::Common::URI::http,它提供的是HTTP URI的正则表达式,它嵌套了两层,第一层的key为URI,这个key对应的值是第二层hash,第二层hash的key为HTTP,于是可以通过$RE{URI}{HTTP}的方式获取这个正则。

例如匹配一个http url是否合理:
use Regexp::Common qw(URI);
while( <> ) {
    print if /$RE{URI}{HTTP}/;
}

在学习shell脚本的时候,经常有人写匹配IPV4的正则表达式,现在我们可用直接从Regexp::Common::net中获取:
use Regexp::Common qw(net);
$ipv4=$RE{net}{IPv4};
print $ipv4;

以下是结果:
(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))

只是需要注意的是,在真正匹配的时候应该将得到的引用锚定起来,否则对318.99.183.11进行匹配的时候也会返回true,因为18.99.183.11是符合匹配结果的。所以对前后都加上锚定,例如:
$ipv4 =~ /^$RE{net}{IPv4}$/;

将上面的ipv4正则改造一下(去掉非捕获分组的功能),让它适用于shell工具中普遍支持的扩展正则:
(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})){3}

默认情况下,Regexp::Common的各个模块是没有开启捕获功能的。如果要使用$1、$N这种引用,需要使用{-keep}选项,至于每个分组捕获的是什么内容,需要参考帮助文档的说明。例如:
use Regexp::Common qw(number);
while( <> ) {
    say $1 if /$RE{num}{int}{ -base => 16 }{-keep}/;
}


六、参考来源

perl引用 声明方式 调用方式 解引用
perl的引用和数据结构