Perl笔记:08、用正则表达式处理文…

1、用s///替换

$_ = "He's out bowling with Barney tonight." ;
s/Barney/Fred/ ; # 将 Barney 替换为 Fred
print "$_\n" ;

当然这里可以使用比较复杂的正则表达式

s /with ( \w + ) /against $1 's team/;
print "$_\n";

输出He's out bowling against Fred's team tonight.

再来看看更多的示例:

$_ = "green scaly dinosaur" ;
s/(\w+) (\w+)/$2, $1/ ; # 替换后为 "scaly, green dinosaur"
s/^/huge, / ; # 替换后为"huge, scaly, green dinosaur"
s/,.*een// ; # 将模式内容清空后为 "huge dinosaur"
s/green/red/ ; # 匹配失败,结果仍为 "huge dinosaur"
s/\w+$/($`!)$&/ ; # 替换后为 "huge (huge !)dinosaur"
s/\s+(!\W+)/$1 / ; # 替换后为 "huge (huge!) dinosaur"
s/huge/gigantic/ ; # 替换后为 "gigantic (huge!) dinosaur"

s///返回的是布尔值,替换成功为真,否则为假。

使用/g进行全局替换

s///的模式只会替换到第一个匹配的结果,但如果在后面加上一个/g情况就发生了变化,它会替换所有匹配的内容。

示例:

$_ = "home, sweet home!" ;
s/home/cave/g ;
print "$_\n" ; # "cave, sweet cave!"

常见的替换应用是将多个空白替换为一个空格,如下:

$_ = "Input data\t may have extra whitespace." ;
s/\s+/ /g ; # 结果为 "Input data may have extra whitespace."

删除开头和结尾的空格
s/^\s+//;
s/\s+$//;

也可以写成:s/^\s+|\s+$//g;

不同的定界符

和m//与qw//一样,我们也可以改变s///的定界符。由于替换运算符要用到三个定界符,因此与先前的例子还有些不同,在使用成对出现的定界符是书写方式略有不同,如果不是成对出现的定界符就无所谓了,如下:

s #^https://#http://#;
s {fred } {barney } ;
s [fred ] (barney ) ;
s <fred> #barney#;

第一个例子由于使用的不是成对出现的定界符,因此使用上与/差不多,后面几个例子使用的都是成对出现的定界符,因此在形式上有些不同,你甚至可以在一个替换模式中使用两种定界符!

可选修饰符

不仅是 /g 操作符,替换运算也可以使用我们经常在模式匹配中使用的 /i 、/x 与 /s 修饰符。它们使用时的顺序对结果没有影响。
s#wilma#Wilma#gi; # 将所有的 WiLmA 或 WILMA 替换为 Wilma
s{__END__.*}{}s; # 将__END__标记之后的所有内容都清除

绑定操作符

像之前m//时提到的,我们可以使用绑定操作符为s///选择不同的目标

$file_name =~ s#^.*/##s; # 将 $file_name变量中所有Unix风格路径全部去掉

大小写转换

这里介绍几个转义字符:
\U 将其后的所有字符转换为大写
\u 将其后的第一个字符转换为大写
\L 将其后所有的字符转为小写
\l 将其后的第一个字符转换为小写
\E 结束大小写转换的影响

示例:

 

$_ = "I saw Barney with Fred." ;
s/(fred|barney)/\U$1/gi ; # $_ 现在成了 "I saw BARNEY with FRED."
s/(fred|barney)/\L$1/gi ; # $_ 现在成了 "I saw barney with fred."
s/(\w+) with (\w+)/\U$2\E with $1/i ; # $_ 替换为 "I saw FRED with barney."
s/(fred|barney)/\u$1/ig ; # $_ 替换后为 "I saw FRED with Barney."
s/(fred|barney)/\u\L$1/ig ; # $_ 这里将首字母变为大写 "I saw Fred with Barney."

这里介绍的转义字符也适用于双引号包含的字符串中,如下:
print "Hello, \L\u$name\E, would you like to play a game?\n";

#!/usr/bin/perl -w
use strict ;

$_ = "I saw Barney with Fred." ;

s/(barney|fred)/\U$1/gi ;

print "$_ \n" ;

s/(barney|fred)/\L$1/gi ;

print "$_\n" ;

s/(\w+) with (\w+)/\U$2\E with $1/gi ;

print "$_\n" ;

s/(barney|fred)/\u$1/gi ;
print "$_\n" ;

s/(barney|fred)/\L\u$1/gi ;
print "$_\n" ;

输出结果:
I saw BARNEY with FRED.
I saw barney with fred.
I saw FRED with barney.
I saw FRED with Barney.
I saw Fred with Barney.

Split操作符

split会根据分隔符拆分一个字符串,如果分割成功,返回的结果是按照分隔符切分的字段,否则返回空。

写法:@fields = split /separator/,$string;

任何匹配模式的内容都不会出现在返回的字段中。下面就是以冒号作为分隔符的典型split模式:
@fields = split /:/, "abc:def:g:h"; # 得到 ("abc", "def", "g", "h")

如果两个分隔符连在一起,会产生空字段:
@fields = split /:/, "abc:def::g:h"; # 得到 ("abc", "def", "", "g", "h")

split 还有一个不成文的规则:它会保留开头处的空字段,但却会省略结尾处的空字段。
@fields = split /:/, ":::a:b:c:::"; # 得到 ("", "", "", "a", "b", "c")

利用/\s+/模式进行空白分隔也是常见的做法。在此模式下,所有的空白会被当成一个空格来处理:

my $some_input = "This is a \t test.\n" ;
my @args = split /\s+/ , $some_input ;
# 得到 ("This", "is", "a", "test.")
# 上面的可以简写为下面的方式,因为split操作符默认是以空白分隔$_变量
my @fields = split ; # 与 split /\s+/, $_; 一致

Join函数

join函数不会使用模式,它的功能与split功能恰好相反,split会将字符串分解为数个子字符串,而join会把这些子字符串合并为一个字符串。用法如下:
my $result = join $glue,@pieces;
join函数的第一个参数是各个字符的分隔符,第二个参数就是需要合并的数组。
my $x = join ":",4,5,6,7,8,9,10,11;
结果$x为"4:5:6:7:8:9:10:11"

join与split的用法类似,但要记住join的第一个参数是字符串而非模式!

列表上下文中的m//

在列表上下文中使用的模式匹配操作符(m//)时,如果模式匹配成功,那么返回的是所有捕获变量的列表;如果匹配失败,则返回空列表:

$_ = "Hello there, neighbor!" ;
my ( $first , $second , $third ) = /(\S+) (\S+), (\S+)/ ;
print "$second is my $third\n" ;

输出结果:there is my neighbor!
如此就能给那些匹配变量起即好记又动听的名字。

先前已经在s///的例子中用到了/g修饰符,该修饰符同样也可以用在m//操作符上,其效果就是让模式能够匹配到字符串中的许多地方。下面的例子中具有一对圆括号的模式,它会在每次匹配成功时返回一个捕获串:

my $text = "Fred dropped a 5 ton granite block on Mr. Slate" ;
my @words = ( $text =~ /([a-z]+)/ig ) ;
print "Result: @words\n" ;

输出结果:Result: Fred dropped a ton granite block on Mr Slate

这就相当于自己动手实现了split的功能,但并不是指定想要去除的部分(split中的第一个参数,也即是分隔符实际上就是要去除的部分),反而是指定想要流行的部分。

如果模式中有多对圆括号,那么每次匹配就能捕获多个串。假设,我想让一个字符串变成哈希,可以这样做:

#!/usr/bin/perl -w

use strict ;

my $letter2name = "a apple b banana c carry
d dog e edit f fllow g good h height i idle"
;

my %newhash = ( $letter2name =~ /(\w+)\s+(\w+)/g ) ;

while ( my ( $key , $value ) = each %newhash ) {
print "$key => $value\n" ;
}

输出结果:
e => edit
a => apple
d => dog
c => carry
h => height
b => banany
g => good
f => fllow
i => idle

注意:本例中的模式匹配中一定要加上修饰符/g 否则就会匹配字符串中一次,这样的话哈希中也就只有一对值。如果加上/g的话就会对整个字符串进行全面的扫描了,从而将字符串完整的转换为哈希。

非贪婪量词

到目前为止我们碰到的4个量词({} + * ?)都是贪婪量词。也就是说在保证整体匹配的前提下,它们会尽量匹配长字符串。与之相对的就是非贪婪量词。对于每一个贪婪的量词,都有一个非贪婪的量词。以加号(+)为例,它的非贪婪量词为(+?),这表示一次或更多匹配(加号的本意)。但是匹配的字符串却是越短越好,而不是越长越好。举例来说:假如我想处理一个HTML文本,需要将去除<BOLD>跟</BOLD>这样的标记,并保留剩余的内容。如果要处理的字符串是这样:
I’m talking about the cartoon with Fred and <BOLD>Wilma</BOLD>!
那么可以用下面的这个替换表达式把标记去掉。
s#<BOLD>(.*)</BOLD>#$1#g;
但这样会不会有问题呢?实际上这个表达式考虑得太简单了,也可以说这里的星号量词太贪心了,如果换成下面的字符串该怎么办呢?
I thought you said Fred and <BOLD>Velma</BOLD>, not <BOLD>Wilma</BOLD>
这种情况下该模式会从第一个<BOLD>匹配直到最后一个</BOLD>,把这中间的部分全部取出来。这就错了,在这里我们就需要使用非贪婪量词。非贪婪版本的星号是*?,所以新的替换表达式应该写成这样:
s#<BOLD>(.*?)</BOLD>#$1#g;
这样就正确无误了!

看到这里你也就知道了剩下的两个量词的非贪婪版本了:
?? 虽然问号已经是匹配零次或一次了,但是这样写的话会优先考虑零次
{5,10}? 优先考虑5次

跨行的模式匹配

传统的正则表达式都是用来匹配单行文本。由于Perl可以处理任意长度的字符串,其模式匹配也可以处理多行文本,与处理单行文本并无差异。看看下面可以表示4行的文本:
$_ = "I'm much better\nthan Barney is\nat bowling,\nWilma.\n";
^和$通常是用来匹配整个字符串的开始和结束的。但当模式加上/m修饰符后,就可以让它们也匹配串内的换行符,这样一来,它们所代表的位置就不再是整个字符串的头尾,而是每行的开头跟结尾。因此,下面的模式就成立了:
print "Found 'wilma' at start of line\n" if /^wilma\b/im;

同样,也可以对多行文本逐个进行替换,参见下面的例子,先把整个文件读进一个变量,然后把文件名前置于每一行的开头:

#!/usr/bin/perl -w

use strict ;

my $filename = "test.log" ;
open MAILLOG , $filename
or die "Can't open $filename:$!" ;

my $line = join '' , <MAILLOG> ;

$line =~ s/^/$filename : /gm ;

print "$line" ;

一次更新多个文件

要在Perl中直接修改文件内容可以使用钻石操作符(<>),先看下面的例子:

#!/usr/bin/perl -w
use strict ;
chomp ( my $date = `date` ) ;
$^I = ".bak" ;
while ( <> ) {
s/^Author:.*/Author: Randal L. Schwartz/ ;
s/^Phone:.*\n// ;
s/^Date:.*/Date: $date/ ;
print ;
}

程序一开始使用了系统的date命令,下一行则是对$^I变量的赋值,这个赋值是个扩展名,也就是再修改的时候要先备份一下,以防万一。
钻石操作符会读取命令行参数指定的那些文件。程序的主循环一次会读取、更新及输出一行

===================本章习题======================

1、写个程序来赋值并修改指定的文本文件。在副本里,此程序会把出现字符串Fred(大小写不计)的每一处都换成Larry。输入文件名应该在命令行上指定,输出文件名是本来的文件名加上.out。
答:

#!/usr/bin/perl -w

use strict ;

my $in = $ARGV [ 0 ] ;

unless ( defined $in ) {
die "Usage $0 filename" ;
}

my $out = $in ;
$out =~ ( s / (\ . \w ) ? $/ .out / ) ;

unless ( open IN , "<$in" ) {
die "Open file $in error:$?" ;
}

unless ( open OUT , ">$out" ) {
die "Write file $out error:$?" ;
}

while ( <IN> ) {
s/fred/Larry/gi ;
print OUT $_ ;
}

2、修改前一题的程序,以便把所有的Fred换成Wilma并把所有的Wilma换成Fred。如果输入的是fred&wilma,那么正确的输出应是Wilma&Fred。
答:

#!/usr/bin/perl -w

use strict ;

my $in = $ARGV [ 0 ] ;

unless ( defined $in ) {
die "Usage $0 filename" ;
}

my $out = $in ;
$out =~ ( s / (\ . \w ) ? $/ .out / ) ;

open IN , "<$in" or die "Open file $in error:$?" ;

unless ( open OUT , ">$out" ) {
die "Write file $out error:$?" ;
}

while ( <IN> ) {
s/fred/\0/gi ;
s/wilma/Fred/gi ;
s/\0/Willma/g ;
print OUT $_ ;
}

注意:这里的替换是不能用正则表达式一步完成的,必须用一个中间量替换一下,然后再替换回来。本例使用\0也就是NULL作为中间量。

3、写个程序,把你目前写过的所有程序都加上版权声明,也就是在第一行的后面加上如下信息:## Copyright (C) 20XX by Yours Truly。你应该直接修改修改文件内容并且做备份。假设你将在命令行指定待修改的文件名。
答:

#!/usr/bin/perl -w

use strict ;

$^I = ".bak" ;

while ( <> ) {
if ( /^#!/ ) {
$_ .= "## Copyright (C) 2010 by Cooper!\n"
}
print ;
}

4、修改前一题程序里的模式,如果文件中已经有版权声明,就不在进行修改。提示:你可能需要知道钻石操作符当前正在读取的文件名称,可以在$ARGV里找到。
答:

为了避免重复加上版权声明,我们得分两次处理文件。第一次,我们需要先建立一个哈希,它的键是文件名称,而它的值是什么无关紧要,为了安全起见,此处将值置为1:

my %file ;
foreach ( @ ARGV ) {
$file { $_ } = 1 ;
}

第二次,我们会吧这个哈希当成待办列表逐个处理,并把已经包含版权声明的文件移除。目前正在读取的文件名称可用$ARGV获取,所以可以直接把它拿来当哈希键:

while ( <> ) {
if ( /^## Copyright/i ) {
delete $file ( $ARGV ) ;
}
}

最后的部分跟之前所写的程序一样,但我们会会事先把@ARGV的内容改掉:

@ ARGV = sort keys %file ;
$^I = ".bak" ;
while ( <> ) {
if ( /^#!/ ) {
$_ .= "## Copyright (C) 2010 by Cooper!\n"
}
print ;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值