您的位置:首页 > 其它

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

2013-10-14 12:54 525 查看

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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: