-
重新理解正则表达式,Perl中的正则表达式其实不是独立的功能,只是内嵌于某些功能中的子语言,
m//
就像是文本编辑器中的搜索功能,Perl同样提供了替换功能:s/正则表达式的模式/替换的内容/
,如果没有用=~
绑定对象,则默认替换的是预置变量$_
demo9-1
:
#!/usr/bin/perl
$_="This is a test.\n";
if($result = s/is/was/){
print $_;
}
print "result:$result";
作为表达式,s///
返回匹配结果的布尔值:
./demo9-1
Thwas is a test.
result:1
-
s///
默认只会替换第一个匹配的位置,如果要实现全部替换,需要修饰符/g
demo9-2
:
#!/usr/bin/perl
$_="This is a test.\n";
s/is/was/g;
print $_;
./demo9-2
Thwas was a test.
m//
中的修饰符在s///
中同样可以使用,包括:
/i
:忽略大小写
/s
:让.
匹配包括换行符在内的任意字符
/x
:允许在模式中插入任意空白和换行
- 和
m//
一样,在s///
中也可以使用捕获变量
demo9-3
:
#!/usr/bin/perl
$_="This is a test.\n";
s/(\w+) (\w+) (\w+) (\w+)/$4 $3 $2 $1/;
print $_;
把捕获的变量逆序输出
./demo9-3
test a is This.
我们也可以把反向引用加进去:
demo9-4
:
#!/usr/bin/perl
$_="This is a test.\n";
s/(is) \1/were/;
print $_;
匹配到is is
./demo9-4
Thwere a test.
命名捕获变量和自动捕获变量同样可以使用:
demo9-5
:
#!/usr/bin/perl
$_="This is a test.\n";
s/(?<be>is)/_$+{be}_/;
print $`."\n";
print $&."\n";
print $'."\n";
print $_;
./demo9-5
Th
is
is a test.
Th_is_ is a test.
-
s///
的定界符可以替换,如果是非成对定界符(比如#
)只需要重复三次来分割圈引模式和圈引替换模式,但如果是成对定界符(比如{}[]
)就需要成对使用两次来区分圈引模式和圈引替换模式:
s#abc#def#
s{abc}[def]
(虽然#
也被用来表示注释, 但如果Perl解析器在期待一个定界符,就不会把#
视作注释的开头)
- 在
s///
的圈引替换模式中,有一组特殊的转义字符可以实现大小写转换:
\U
:之后的字符全部转换成大写
\L
:之后的字符全部转换为小写
\E
:结束\U
和\L
的作用范围
\u
:之后的第一个字符大写
\l
:之后的第一个字符小写
可以将\u\L
或者\l\U
连用实现首字母大写或首字母小写的功能:
demo9-6
:
#!/usr/bin/perl
$_="This is a test.\n";
print $_;
s/(?<be>test)/\u\L$+{be}/;
print $_;
./demo9-6
This is a test.
This is a Test.
大小写转义字符的规则并不是s///
专属的,任何双引号内的字符串都适用此规则
demo9-7
:
#!/usr/bin/perl
$_="This is a \u\Ltest.\n";
print $_;
s/(?<be>test)/\l\Utest/i;
print $_;
./demo9-7
This is a Test.
This is a tEST.
- 另一个使用正则表达式的是
@result = split //,$string
,操作符split
会根据分隔符(写成模式)将一个字符串截断成子字符串列表。
demo9-8
:
#!/usr/bin/perl
$input = "Zhao:Qian:Sun:LI:Zhou:Wu:Zheng:Wang";
foreach (split /:/, $input){
print $_."\n";
}
分隔符自己不会出现在列表中
./demo9-8
Zhao
Qian
Sun
LI
Zhou
Wu
Zheng
Wang
- 如果字符串中两个分隔符连着就会产生空字段,但是Perl会忽略末尾的空字段:
demo9-9
:
#!/usr/bin/perl
$input = "::Zhao::Qian::Sun::LI::Zhou::Wu::Zheng::Wang::";
foreach (split /:/, $input){
print $_."\n";
}
在原来的每个字段之间和前后都加了额外的分隔符,但是结尾的空字段被舍弃了
./demo9-9
Zhao
Qian
Sun
LI
Zhou
Wu
Zheng
Wang
-
split
的默认行为是用空白\s+
分割存放于$_
内的字符串:
demo9-10
:
#!/usr/bin/perl
$_ = " Zhao Qian Sun LI Zhou Wu Zheng Wang ";
foreach (split){
print $_."\n";
}
稍有不同之处在于所有空字段都被舍弃(因为连续的空白字符本身就匹配了\s+
模式,所以不存在字符串内部的空字段):
./demo9-10
Zhao
Qian
Sun
LI
Zhou
Wu
Zheng
Wang
标准的写法可能是 split /\s+/, $_;
demo9-11
:
#!/usr/bin/perl
$_ = " Zhao Qian Sun LI Zhou Wu Zheng Wang ";
foreach (split /\s+/, $_){
print $_."\n";
}
这样则可以保留字符串前面的空字段,别忘了连续的空白字符本身就匹配了\s+
模式,所以不存在字符串内部的空字段
./demo9-11
Zhao
Qian
Sun
LI
Zhou
Wu
Zheng
Wang
- 和
split
操作符功能相反的是join
函数(没错,split
是操作符,但是join
是函数),不过join
并没有使用模式:
$result = join $glue, @pieces;
join
函数的功能是把@pieces
中的字符串片段用$glue
胶水粘合成一个字符串$result
,$glue
保存着充当胶水的字符串而不是模式
join
可以和split
结合实现替换功能,效果类似s///g
:
demo9-12
:
#!/usr/bin/perl
$_ = " Zhao Qian Sun LI Zhou Wu Zheng Wang ";
@pieces = split;
print join "&", @pieces;
print "\n";
用&
替换了空格
./demo9-12
Zhao&Qian&Sun&LI&Zhou&Wu&Zheng&Wang
- 在列表上下文中使用
m//
会得到有趣的新特性:如果模式匹配成功,会返回所有捕获变量的列表
demo9-13
:
#!/usr/bin/perl
$_ = "Zhang san Li si Wang wu ";
@pieces = /([A-Z][a-z]+)/g;
foreach (@pieces){
print $_."\n";
}
捕获了([A-Z][a-z]+)
首字母大写其余字母小写的单词:
Zhang
Li
Wang
如果有多个捕获模式就会返回多个捕获串:
demo9-14
:
#!/usr/bin/perl
$_ = "Zhang san Li si Wang wu ";
@pieces = /([A-Z][a-z]+)\s+(\w+)/g;
foreach (@pieces){
print $_."\n";
}
返回的列表里包含了所有捕获串
./demo9-14
Zhang
san
Li
si
Wang
wu
特别地,如果捕获的模式恰好是两个,则可以用哈希结构存储m//
返回的结果,这种情况下,第一个捕获的变量是哈希的键,第二个捕获的是哈希的值:
demo9-15
:
#!/usr/bin/perl
$input = "Zhang san Li si Wang wu ";
%hash = ($input =~ /([A-Z][a-z]+)\s+(\w+)/g);
foreach (keys %hash){
print $_."=>".$hash{$_}."\n";
}
./demo9-15
Wang=>wu
Li=>si
Zhang=>san
- 正则表达式中的量词:
*, +, ?, {M,N}
默认都是贪婪量词,即会尽可能多的匹配,总会匹配到最大的字符串,而如果想要使用非贪婪量词,只需要在原来的量词后面加上?
:*?, +?, ??, {M,N}?
,这种情况下会尽可能少的匹配:
demo9-16
:
#!/usr/bin/perl
$_ = "TAG a b c d ef ghi jk TAG lmn opq TAG rst uvw xyz TAG";
if(m/TAG (.+) TAG/){
print "Greedy:".$&."\n";
}
if(m/TAG (.+?) TAG/){
print "Non-greedy:".$&."\n";
}
./demo9-16
Greedy:TAG a b c d ef ghi jk TAG lmn opq TAG rst uvw xyz TAG
Non-greedy:TAG a b c d ef ghi jk TAG
- 使用钻石操作符的新特性,结合正则表达式,使用Perl来修改文件:
当$^I
变量中是字符串时,将赋予钻石操作符<>
新的特性:首先将@ARGV
中字符串对应的文件改名,加上后缀,后缀内容即是$^I
内存储的值,修改输入指向这个文件,然后打开一个新文件,命名为原来的文件名,修改输出到这个新文件。
demo9-17
:
#!/usr/bin/perl -w
use strict;
$^I=".bak";
chomp(my $date = `date`);
while(<>){
s/Date:.*/Date:$date/;
print;
}
另外创建一个文件写上“Date:”, 则每次运行程序,都会把Date后的时间更新为当前时间
echo "Date: " > test
./demo9-17 test
这时目录下生成一个test.bak
文件,存储着修改前的内容。 while
循环里每次都会从test.bak
里读取一行放到$_
里,然后用s///
修改后,打印到test
文件里(输出已经被<>
重定向到新文件里了)
如果$^I
为空,则修改就会直接作用在源文件上
因为这个程序太普遍,所以有了它的简化版本:
perl -p -i.bak -w -e 'chomp(my $date=`date`);' -e 's/Date:.*/Date:$date/' tes*
可以直接在命令行执行,作用和 demo9-17
相同
-p
选项让perl自动生成一段类似下面这样的小程序:
while(<>){
print;
}
-e
后面跟着程序代码,会插入到while循环之中
-i.bak
和$^I=".bak"
的作用相同,如果只有-i
那就像是把$^I
变量置为空,则不会有备份动作发生。
最后一个参数表示设定@ARGV
的值,这里使用了通配符