#第九章:用正则表达式处理文本
用s///进行替换
s///查找替换功能
s/Barney/Fred/; #把Barney替换成Fred
s/with (\w+)/against $1's team/;
例子:
$_ = "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"
用/g进行全局替换
s///只会一次替换,默认的行为
$_ = "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; #去除开头和结尾的空白符,但运行可能会慢,由于perl的引擎问题
不同的定界符:
替换时,也可以像m//和qw//一样,我们也可以改变s///的定界符,但由于替换符会用到三个定界符,所以情况又有点不同。
s#^https://#http://#
s{fred}{barney};
s[fred](barney);
s<fred>#barney#;
可用替换修饰符:
s#wilma#Wilma#gi; #将所有的WilmA或者WILMA一律替换为Wilma
s{__END__.*}; #将__END__标记和它后面的所有内容都删掉
绑定操作符:
$file_name =~ s#^.*/##s; #将$file_name中所有的Unix风格的路径全部去除
无损替换:
如果需要同时保留原始字符串和替换后的字符串,该怎么办?传统的做法是先复制一份拷贝后再替换:
my $original = 'Fred ate 1 rib';
my $copy = $original;
$copy =~ s/\d+ ribs?/10 ribs/;
替换成一步:
(my $copy = $original) =~ s/\d+ ribs?/10 ribs/;
新写法:
use 5.014;
my $copy = $original =~ s/\d+ ribs?/10 ribs/r; #先做替换,再复制
大小写切换:
\U: 使目标变为大写
$_ = "I saw Barney with Fred.";
s/(fred|barney)/\U$1/gi; #$_现在成了"I saw barney with fred."
\L: 使目标变为小写
s/(fred|barney)/\L$1/gi; #$_现在成了"I saw barney with fred"
\E: 关闭大小写转换,不然会影响后面的字符
s/(\w+) with (\w+)/\U$2\E with $1/i; #$_现在成了"I saw FRED with barney"
使用小写形式(\l与\u)时,他们只会影响紧跟其后的第一个字符:
s/(fred|barney)/\U$1/ig; #$_替换后为"I saw Fred with Barney"
s/(fred|barney)/\u\L$1/ig; # 第一个字母大写,其它字符小写
也可以用在双引号的字符串中:
print "Hello, \L\u$name\E, would you like to play a game?\n"
split操作符:
根据给定的模式拆分字符串,对于使用制表符、冒号、空白或任意符号分割不同字段数据的字符串来说,这个操作符分解提取字段相当方便,只要你能将分隔符写成模式,就可以使用split分解数据,它的使用用法如下:
my @fields = split /separator/, $string;
my @fileds = split/:/, "abc:def:g:h"; #得到("abc", "def". "g", "h")
如果两个分隔符连在一起,就会产生空字段:
my @fields = split/:/, "abc:def:g:h"; #得到("abc", "def", "", "g", "h")
以CPAN上的Text::CSV库处理csv文件最好。
split会保留开头处的空字段,却会舍弃结尾处的空字段。
my @field = split /:/, ":::a:b:c:::"; #得到("", "", "", "a", "b", "c")
根据split的/\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函数恰好相反,他会将这些片段接合成一个字符串。join函数的用法如下:
my $result = join /$glue/, @pieces;
my $x = join ":", 4, 6, 8, 10, 12; # $x为"4:6:8:10:12"
列表上下文中的m//:
在列表上下文中使用模式匹配操作符(m//)时,如果模式匹配成功,那么返回的是所有捕获变量的列表;如果匹配失败,则会返回空列表:
$_ = "Hello there, neighbor!";
my($first, $second, $third) = /(\S+) (\S+) (\S+)/;
print "$second is my $third\n";
用等号,默认匹配的是$_
之前在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";
#打印:fred dropped a ton granite block on Mr. Slate
这好比反过来用split:正则模式制定的并非想要去除的部分,反而是要留下的部分。
一个字符串变成hash
my $data = "Barney Rubble Fred Flintstone Wilma Fllintstone";
my %last_name = (data =~ /(\W+)\s+(\w+)/g);
更强大的正则表达式:
非贪婪量词:
/fred.+barney/匹配fred, and braney bowling
因为.+是贪婪的会匹配更多的单词,会认为barney单词然后跳过了
/fred.+?barney/匹配fred and braney
回溯动作,逐个匹配需要的内容
CPAN上的模块可以处理HTML或类似的标记语言,使用模块为HTML::Parser
贪婪样式:
s#<BOLD>(.*)</BOLD>#$1#g;
非贪婪样式:
s#<BOLD>(.*?)</BOLD>#$1#g;
非贪婪样式是在通配符后面加上?,如+?,*?,{5,10}?或者{8, }?, ??
跨行的模式匹配:
传统的正则表达式是处理单行文本,perl可以处理任意长度的字符串,其模式匹配自然也可以处理多行文本
下面的写法可以表示4行的文本:
$_ = "I'm much better \nthan Barney is \nat bowling, \nWilma.\n";
^代表开头,$代表结尾,m代表匹配多行(m可看做multiple lines),
如:print "Found 'wilma' at start of line\n" if /^wilma\b/im;
open FILE, $filename or die "Can't open '$filename':$|";
my $lines = join '', <FILE>;
$lines =~ s/^/$filename: /gm;
ps:将整个文件读进一个变量,然后把文件名作为每一行的前缀进行替换。
一次更新多个文件:
下面的程序有问题,待解决:$^I好像并不是这样的用法
#!/usr/bin/perl -w
use strict;
chomp(my $date = `date`); #用了bash shell里的date指令
$^I = ".bak"; #能循环找当前文件夹下的文件????
while(<>)
{
s/^Author:.*/Author: Randal L. Schwartz/;
s/^phone:.*\n//;
s/^Date:.*/Date: $date/;
print;
}
my $date = localtime;
钻石操作符会读取命令行参指定的那些文件,程序的主循环一次会读取,更新及输出一行
从命令行直接编辑:
假设你需要更新上百个文件,把里面拼错成Randall的名字改成只有一个l的Randal。你可以写个和之前类似的程序完成此事,或者在命令行上使用如下单行程序一步完成。
$perl -p -i.bak -w -e 's/Randall/Randal/g' fred*.dat