学习Perl(5)
继续我的Perl之旅,哈哈
3. 以正则表达式处理文本
1)以s///进行替换
如果把m//模式匹配操作符想成文字处理器的“查找”功能,那么“查找与替换”的功能就是Perl的s///替换操作符:
#!/usr/bin/perl -w
$_ = "He's out bowling with Barney tonight.";
s/Barney/Fred/; #用Fred替换Barney
print "$_n";
下面的替换用到了第一个内存变量,也就是$1,进行模式匹配时它会被赋值:
#!/usr/bin/perl -w
$_ = "He's out bowling with Barney tonight.";
s/with (w+)/against $1/;
print "$_n";
s///会返回有用的布尔值。它在替换成功时为“真”,否则为“假”:
#!/usr/bin/perl -w
$_ = "fred flintstone";
if (s/fred/wilma/) {
print "Successfully replaced fred with wilma!n";
}
注意,即使有其他可以替换的部分,s///也只会替换一次。
2)以/g进行全局替换:
/g修饰符可让s///进行所有可能的、互不重叠的替换:
#!/usr/bin/perl -w
$_ = "home,sweet home!";
s/home/cave/g;
print "$_n";
一个相当常见的全局替换是缩减空白,也就是将任何连续的空白转换成单一空格:
#!/usr/bin/perl -w
$_ = "Input datat may have extra whitespace.";
s/s+/ /g;
print "$_n";
删除开头和结尾处的空白:s/^s+//; s/s+$//;
一个步骤的写法则是使用择一匹配的竖线符号并配合/g修饰符:
s/^s+|s+$//g; #去掉头尾的空白
3)不同的界定符号
对于一般没有左右之分的(非成对)字符,用法跟使用斜线一样,只使用3个界定符号即可:
S#^https://#http://#;
但如果使用有左右之分的成对字符,就必须使用两对:一对包住模式,一对包住替换字符串:
s{fred}{barney}; s[fred](barney); s
4)选项修饰符和绑定操作符
s#wilma#Wilma#gi; s{_END_.*}{}s; #删除结尾标记及之后的每一行
$file_name =~ s#^.*/##s; #移除$file_name里所有Unix样式的路径
5)大小写转换
U转义字符会将其后的所有字符转换成大写:
#!/usr/bin/perl -w
$_ = "I saw Barney with Fred.";
s/(fred|barney)/U$1/gi;
print "$_n";
L转义字符会将其后的所有字符转换成小写;
#!/usr/bin/perl -w
$_ = "I saw Barney with Fred.";
s/(fred|barney)/L$1/gi;
print "$_n";
也可以使用E关闭大小写转换的功能;
s/(w+) with (w+)/u$2E with $1/i;
使用小写形式(l与u)时,它们只会影响之后的第一个字符:
s/(fred|barney)/u$1/ig;
甚至可以将它们并用,如使用u和L来表示“全部转小写,但首字母大写”:
s/(fred|barney)/uL$1/ig;
6)split操作符
split操作符会根据分隔符拆开一个字符串,这对处理以制表符、冒号、空白或任意符号所分隔的数据相当有用。它的用法如下所示:@fields = split /separator/, $string;
split操作符将会以分隔符的模式扫过所指定的字符串($string),并且返回由该模式所分隔出来的一串字段(子字符串)。下面以冒号做分隔符,是个典型的split模式:
@fields = split /:/, “abc:def:g:h”; #产生(”abc”,”def”,”g”,”h”)
如果两个分隔符放在一起,就会产生空的字段:
@fields = split /:/, “abc:def::g:h”; #产生(”abc”,”def”,””,”g”,”h”)
注意:split会保留开头处的空字段,却会舍弃结尾处的空字段。
利用/s+/模式以空白进行分隔也是常见的做法。在此模式下,所有的空白会被当成一个空格来处理:my $some_input = “This is a t test.n”;
Split默认会以空白字符拆开$_:(用一个空格来取代模式是split的特别用法)
my @fields = split; #等于 split /s+/,$_;
7)join函数
Join函数所达成的效果正好与split相反,它的用法如下所示:
my $result = join $glue,@pieces;
join会把胶合字符串放进每个片段之间并且返回所得到的字符串:
my $x = join “:”, 4,6,8,10,12; # $x为”4:6:8:10:12”
使用上面的$x,我们可以先分解字符串,再用不同的界定符号将它接起来:
my @value = split /:/,$x; my $z = join “-“,@value;
注意:虽然split与join合作无间,但是join的第一个参数是字符串,而不是模式!
8)列表上下文中的m//
在列表上下文中使用匹配操作符(m//)时,如果模式匹配成功,则其所返回的列表内容是所有内存变量的内容;如果匹配失败,则返回空列表:
#!/usr/bin/perl -w
$_ = "Hello there, neighbor!";
my($first, $second, $third) = /(S+) (S+), (S+)/;
print "$second is my $thirdn";
9)功能更强大的正则表达式
非贪心的量词
贪心的量词,/fred.+barney/ “回溯”
对于每一个贪心的量词,都会存在一个非贪心的量词。以加号(+)来说,我们可以改用非贪心的量词+?,这表示至少有一次(跟加号一样),但是所匹配到的字符串却是越短越好,而不再是越长越好。 /fred.+?barney/
如果要处理的字符串是这样;
I’am talking about the cartoon with Fred and
应该使用非贪心的量词,s#
跨行的模式匹配
下面的写法可以表示一个4行文本:
$_ = “I’m much betternthan Barney isnat bowling,nWilma.n”;
每一行的开头和结尾:print “Found ‘wilma’ at start of linen” if /^wilman/im;
下面的程序会先把整个文件读进一个变量,然后把其他的文件名前置在每一行的开头:
open FILE,$filename
or die “Can’t open ‘$filename’: $!”;
my $lines = join ‘’,
$lines =~ s/^/$filename: /gm;
一次更新多个文件
要在Perl中直接修改文件内容,可利用钻石操作符(<>)。
修改几百个格式类似的文件,只做三项改动:
#!/usr/bin/perl -w
use strict;
chomp(my $date = `date`);
$^I = “.bak”;
while (<>) {
s/^Author:.*/Author: Randal L. Schwwartz/;
s/^Phone:.*n//;
s/^Date:.*/Date: $date/;
print;
}
只从命令行来修改文件
在命令行上使用单行程序修改
$ perl -p -i.bak -w -e ‘s/Randall/Randal/g’ fred*.dat
Perl的命令选项:
Perl的作用如同在文件的开头写上#!/usr/bin/perl,表示以perl程序来处理随后的事项;
-p选项用来要求Perl帮你写程序,如同while (<>) { print;}
-i.bak选项,其作用是在程序开始运行之前把$^I设为”.bak”,做个备份;
-w选项,开启警告功能;
-e选项用来告诉Perl,“程序代码来了”;
最后一个参数是fred*.dat,表示@ARGV的值应该是匹配此文件名模式的所有文件名;
把以上所有片段都组合在一起,就好像写了下面这个程序以及把fred*.dat参数传给它一样:
#!/usr/bin/perl –w
$^I = ”.bak”;
while (<>) {
s/Randall/Randal/g;
print;
}
4. 其他控制结构
1)Unless控制结构和until控制结构
使用unless表示除非这个条件式为“真”,否则运行这对程序代码,它和if测试一样,只是条件相反;如果你想让程序块在条件式为“假”时执行,请将if换成unless。
你甚至可以在unless之后加上一个else子句;
Unless可以用if来改写。
Until循环结构会一直执行,直到条件式为“真”,是改装过的while循环。
2)表达式修饰符
为了简化表达的方式,表达式后面可以接着一个用来控制它的修饰符:
print “$n is a negative number.n” if $n < 0;
注意到,即使表达式写在后面,它仍然会先执行。
3)未修饰块的控制结构
所谓的“未修饰”块,就是没有关键字或条件式的块。它的块内容只会被执行一次,是一个不循环的循环!
为临时局部变量提供一个有效的范围,是它其中的一个特色:
#!/usr/bin/perl -w
{
print "Please enter a number:";
chomp(my $n =
my $root = sqrt $n;
print "The square root of $n is $root.n";
}
4)自动递增与自动递减
快速而简单的方法,用来检查列表中有哪些项目并且计算每个项目出现了几次:
my @people = qw{ fred barney wilma dino barney fred pebbles };
my %count ; #新的空散列
$count{$_}++ foreach @people; #必要时创建新的键与值
5)for控制结构
For循环目前最常见的用途,就是控制重复的运算过程:
#!/usr/bin/perl -w
for ($i = 1;$i <=10; $i++) {
print "I can count to $i!n";
}
#!/usr/bin/perl -w
for ($_ = "bedrock";s/(.)//; ) {
print "One character is $1n";
}
Foreach和for之间的秘密关联
事实上,在Perl的解析器里这两个关键字是等价的。
如果圆括号里有两个分号,它会被当成for循环;否则,它其实是foreach循环:
for (110) { #事实上,这是从1到10的foreach循环
print “I can count to $_!n”;
}
在Perl里,真正的foreach循环几乎总是比较好的选择。
6)循环控制
last操作符会立即循环的终止(类似C语言的break),它是循环块的紧急出口。Last操作符会对整个循环其作用;last操作符会对当前运行中的最内层的循环块发挥作用。
next操作符会跳到当前循环块的底端,在next之后,程序会继续执行循环的下次迭代(类似C语言的continue);next只对最内层循环起作用。
redo操作符会跳到当前循环块的顶端,而不经过任何测试条件,也不会前进到循环的下一次迭代;只对最内层循环起作用。
示范程序,体验这3个操作符的运作方式:
#!/usr/bin/perl -w
foreach (1..10) {
print "Iteration number $_.nn";
print "Please choose:last,next,redo,or none of above?";
chomp(my $choice =
print "n";
last if $choice =~ /last/i;
next if $choice =~ /next/i;
redo if $choice =~ /redo/i;
print "That wasn't any of the choices ... onward!nn";
}
print "That's all,folks!n";
习题:
写一支程序,让用户不断猜测范围从1到100的秘密数字,直到猜中为止。程序以魔术公式int(1 + rand 100)来随机产生秘密数字……
#!/usr/bin/perl -w
my $secret = int(1 + rand 100);
#print "Don't tell anyone,but the secret number is $secret.n";
while (1) {
print "Please enter a guess from 1 to 100:";
chomp(my $guess =
if ($guess =~ /quit|exit|^s*$/i) {
print "Sorry you give up.The number was $secret.n";
last;
} elsif ($guess < $secret) {
print "Too small.Try again!n";
} elsif ($guess == $secret) {
print "That was it.Congratulations!n";
} elsif ($guess > $secret) {
print "Too large.Try again!n";
}
}
5. 文件测试
1)文件测试操作符
启动会创建新文件的程序之前,先检查所指定的文件是否真的存在,以免误操作给覆盖掉了,可以用-e文件测试操作符来测试文件是否存在:
die “Opps! A file called ‘$filename’ already exists.n”
if -e $filename;
文件测试符组成自连字符和某个字母(代表要进行何种测试),后面接着所要测试的文件名或文件句柄。-r -w -x -o -e -z -s -f -d -l -t -T -B -M -A -C
2)关于stat函数和lstat函数
想知道文件所有其他的相关信息,可以使用stat函数,这个函数会返回相当丰富的信息。函数stat的参数可以是文件句柄或是某个会返回文件名称的表达式。它所返回的值可能是空列表,表示函数stat执行失败;也可能是个含有13个元素的数字列表: $dev与$ino,$mode,$nlink,$uid和$gid,$size,$atime、$mtime和$ctime,$blksize,$blocks
若你需要符号链接本身的信息,可以使用lstat函数来代替stat。
3)localtime函数
Perl可以在标量上下文中使用localtime函数来完成转换:
my $timestamp = 1180630098; my $date = localtime $timestamp;
gmtime函数返回的是世界标准时间(格林尼治标准时间);如果需要从系统时钟取得当前的时间戳,请使用time函数。 my $now = gmtime;
4)使用特殊的“下划线文件句柄”
对特殊的_文件句柄进行stat、lstat或文件测试(也就是仅以单一的下划线为操作符),就是在告诉Perl从内存里找出前一次文件测试、stat或lstat的参考数据来用,而不是到外面向操作系统再要一次数据。
习题:
1):写一支程序,从命令行取得一串文件名称,并汇报这些文件是否可读、可写、可执行以及是否存在:
#!/usr/bin/perl -w
foreach my $file (@ARGV) {
my $attribs = &attributes($file);
print "'$file' $attribs.n";
}
sub attributes {
my $file = shift @_;
return "does not exist" unless -e $file;
my @attrib;
push @attrib, "readable" if -r $file;
push @attrib, "writable" if -w $file;
push @attrib, "executable" if -x $file;
return " exests "unless @attrib;
'is' . join " and ",@attrib;
}
2)写一支程序,从命令行参数所指定的文件中找出最旧的文件并且以天数汇报它已经存在多久了:
#!/usr/bin/perl -w
die "No file names supplied!n" unless @ARGV;
my $oldest_name = shift @ARGV;
my $oldest_age = -M $oldest_name;
foreach (@ARGV) {
my $age = -M;
($oldest_name,$oldest_age) = ($_,$age)
if $age > $oldest_age;
}
printf "The oldest file was %s,and it was %.1f days old.n",$oldest_name, $oldest_age;