使用正则表达式替换文本$var=~s/regex/replacement/
在前面我们遇到的例子中都是从字符串中提取
信息。现在来看Perl
和其他许多语言都提供的以正则表达式的特性:替换(substitution
)也可以叫查找和替换(search and replace
).
我们已经看到$var=~m/regex/
尝试用正则表达式来匹配保存在变量中的字符串,并返回是否能够匹配的布尔值。与之类似的结构$var=~s/regex/replacement/
则更近一步:如果正则表达式能够匹配$var
中的某段子字符串,则将这个子字符串替换为replacement
。其中regex
和之前的m/.../
的用法一样。而replacement
(位于第2个斜线和第3个斜线之间)则是作为双引号内的字符串。
所以使用$var=~s/.../.../
可以改变变量$var
中的文本,当然如果没有在变量$var
中找不到匹配的文本,也就不会发生替换。
例如$var="Jeff·Friedl"
(这是本书的作者),执行$var=~s/Jeff/Jeffrey/;
则$var
的值就变成了Jeffrey·Friedl
,如果再执行一次,则$var
的值就变成了Jeffreyrey·Friedl
。代码执行效果如下。
replace.pl
#!/usr/bin/perl
$var="Jeff·Friedl";
print "替换之前:$var\n";
$var=~s/Jeff/Jeffrey/;
print "替换1次之后:$var\n";
$var=~s/Jeff/Jeffrey/;
print "替换2次之后:$var\n";
~
运行:perl replace.pl
,运行结果:
替换之前:Jeff·Friedl
替换1次之后:Jeffrey·Friedl
替换2次之后:Jeffreyrey·Friedl
为了避免这种重复替换的情况,可以加入表示单词分界的元字符,在第一章中提到,在某些版本的egrep
支持\<
和\>
作为单词起始
和单词结束
的元字符。
类似的Perl
中提供了\b
来代替两者。例如$var="Jeff·Friedl"
,执行$var=~s/\bJeff\b/Jeffrey/;
,因为在文本Jeff·Friedl
中有单词Jeff
,所以把这个单词替换成,Jeffrey
,所以$var
中的文本为Jeffrey·Friedl
,再次执行$var=~s/\bJeff\b/Jeffrey/;
,因为此时在Jeffrey·Friedl
找不到Jeff
这个单词,所以正则表达式匹配失败,进而不替换,所以这次$var
中的文本不改变,还是Jeffrey·Friedl
replace1.pl:
#!/usr/bin/perl
$var="Jeff·Friedl";
print "替换之前:$var\n";
if($var=~s/\bJeff\b/Jeffrey/)
{
print " 替换成功\n";
}
else
{
print " 替换失败\n";
}
print "执行1次替换之后:$var\n";
if($var=~s/\bJeff\b/Jeffrey/)
{
print " 替换成功\n";
}
else
{
print " 替换失败\n";
}
print "执行2次替换之后:$var\n";
运行结果:
替换之前:Jeff·Friedl
替换成功
执行1次替换之后:Jeffrey·Friedl
替换失败
执行2次替换之后:Jeffrey·Friedl
可以看到加入单词分界符\b
之后第二次替换失败了,所以这样就解决了重复替换的问题。
忽略大小写的替换:$var=~s/regex/replacement/i
与之前的m/.../
一样,s/.../.../
也可以有修饰符,例如使用修饰符i
这样就可以匹配的时候忽略大小写。例如$var="Jeff·Friedl";
执行一次$var=~s/\bjeff\b/JEFF/i
后$var
将变成JEFF·Friedl
。你可能发现了原来$var
中是没有jeff
这个文本的,不过有Jeff
这个文本,由于使用了修饰符i
在匹配的时候,忽略了大小写,所以,所以,jeff
成功匹配到Jeff
,那就把Jeff
替换成JEFF
这个文本,所以执行之后的$var
为JEFF·Friedl
。代码如下。
replace2.pl
#!/usr/bin/perl
$var="Jeff·Friedl";
print "替换之前:$var\n";
if($var=~s/\bJeff\b/JEFF/i)
{
print " 替换成功\n";
}
else
{
print " 替换失败\n";
}
print "替换之后:$var\n";
执行:perl replace2.pl
,执行结果如下:
替换之前:Jeff·Friedl
替换成功
替换之后:JEFF·Friedl
例子:公函生成程序
下面这个有趣的例子展示了文本替换的用途。假设有一个公函系统,它包含很多公函模板,其中有一些标记,对每一封公函来说,标记部分的值有所不同。
这里有一个例子:
Dear =FIRST=,
You have been chosen to win a brand new =TRINKET=! Free!
Could you use another =TRINKET= in the =FAMILY= household?
Yes =SUCKER=, I bet you could! Just respond by...
对于特定的接收人,变量的值分别为:
$given = "Tom";
$family = "Cruise";
$wunderprize = "100% genuine faux diamond";
然后使用下面的语句填写模板
letter=~s/=FIRST=/$given/g;
letter=~s/=FAMILY=/$family/g;
letter=~s/=SUCKER=/$given $family/g
letter=~s/=TRINKET=/fabulous wunderprize/g;
其中每个正则表达式首先搜索简单标记,找到之后用指定的文本替换它,用于替换的文本其实是Perl中的字符串,所以它们能够使用存放文本的变量。例如letter=~s/=FIRST=/$given/g;
程序运行时回把=FIRST=
替换成$given
变量中的字符串,也就是把=FIRST=
替换成Tom
。
其实如果生成一份公函,完全可以不用变量替换,直接照需要的样子生成就是了。但是使用变量替换能够实现自动化的操作,例如可以从一个清单读入信息到上面的变量中,然后再执行上面的替换操作,这样就能生成多个公函。
全局替换修饰符/g
上面的/g
是表示全局替换(global replacement)的修饰符。它告诉s/…/在第一次替换完成之后继续搜索更多的匹配文本,进行更多的替换。如果需要检查的字符串包含多行需要替换的文本,每条规则都对所有行生效,就必/g
修饰符。
officialLetter.pl:
#!/usr/bin/perl
$template='Dear =FIRST=,
You have been chosen to win a brand new =TRINKET=! Free!
Could you use another =TRINKET= in the =FAMILY= household?
Yes =SUCKER=, I bet you could! Just respond by...
';
print "模板:\n";
print "-------------------------------------\n";
print($template);
print "-------------------------------------\n";
$given = "Tom";
$family = "Cruise";
$wunderprize = "100% genuine faux diamond";
$template=~s/=FIRST=/$given/g;
$template=~s/=FAMILY=/$family/g;
$template=~s/=SUCKER=/$given $family/g;
$template=~s/=TRINKET=/fabulous $wunderprize/g;
print "-------------------------------------\n";
print($template);
print "-------------------------------------\n";
运行效果如下:
模板:
-------------------------------------
Dear =FIRST=,
You have been chosen to win a brand new =TRINKET=! Free!
Could you use another =TRINKET= in the =FAMILY= household?
Yes =SUCKER=, I bet you could! Just respond by...
-------------------------------------
-------------------------------------
Dear Tom,
You have been chosen to win a brand new fabulous 100% genuine faux diamond! Free!
Could you use another fabulous 100% genuine faux diamond in the Cruise household?
Yes Tom Cruise, I bet you could! Just respond by...
-------------------------------------
修改股票价格
另一个例子是,我在使用Perl编写的股票价格软件时,遇到的问题。我得到的价格看起来是这样的:
9.0500000037272
。其实这里的价格应该是9.05
,但是因为计算机内部表示浮点数的原理,perl有时会以没什么用的格式输出这样的结果。我们可以像前面温度转换例子中的那样,用printf来保证只输出两位数,但是这里用printf其实不适合。
这是因为当时股票是以分数的形式给出的,如果某个价格以1/8
结尾,则应该输出3
位的小数(.125
)而不是两位小数。
我把自己的要求归结为:通常是保留小数点后两位数字,如果第三位不为零,也需要保留,剩下的其他小数点后面的舍弃。
例如:12.3750000000392
,和12.375
这两个数,会被修正为12.375
,而37.500
则会被修正为37.50
这个就是我想要的结果。那么我们该如何做呢?
$price
变量包含了需要修正字符串,让我们用这个表达式:
$price=~s/(\.\d\d[1-9]?)\d*/$1/
开始的\.
匹配小数点,接下来的\d\d
匹配开头的两个字母。[1-9]?
匹配可能跟在后面的非零的数字。到这里,这些都是我们希望保留的文本,所以我们用括号把这些捕获保存在$1
中。然后将$1
放入replacement字符串中。如果匹配的文本中只匹配了$1
也就是匹配(\.\d\d[1-9]?)
,然后就用$1
替换。这样就等于自己替换自己,这样做是没什么意义的。例如12.375
会被12.357
替换掉(自己替换自己)
不过如果在$1
的括号之外还有其他数字,因为这些数字没有出现在replacement中,所以后面这些数字会被”删除掉”。这就是说被删除的是其他多余的数字,也是就正则表达式末尾的\d*
匹配的字符。例如:12.3570001123456
则会被替换成12.375
,相当于的那些数字被删除掉了。
实例如下:
replaceNum.pl:
#!/usr/bin/perl
$price=12.375000000000100;
print "$price";
$price=~s/(\.\d\d[1-9]?)\d*/$1/;
print "被修正为:$price\n";
$price=37.5001;
print "$price";
$price=~s/(\.\d\d[1-9]?)\d*/$1/;
print "被修正为:$price\n";
运行:perl replaceNum.pl ,运行结果:
12.3750000000001被修正为:12.375
37.5001被修正为:37.50
总结:拿我的一部分来替换我,可以实现删除该文本中一些不必显示的效果。
自动编辑操作
写作本文章是,我遇到了另一个简单但真实存在的例子。当我需要登录到太平洋对岸的一台机器上,但是网速非常慢,按下一个回车键要等一分多钟才能见到反应。而我只需要对某个文本进行一些小的改动,运行一个重要的程序。
实际上,我要做的只是将所有出现的sysread改为read。改动的次数并不多,但因为网络太慢,使用全屏便捷器显然是不可能的。
下面是我的办法:
prel -p -i -e 's/sysread/read/g' file
这条命令中的Perl程序是
s/sysread/read/g
(使得,这就是一个完整的Perl程序—-参数-e
表示整个程序接在命令的后面)。参数-p
表示对目标文件的每一行进行查找和替换,而-i
表示将替换的结果回写到文件中。请注意这里没有明确写出查找和替换的文本(就是说没有,
$var=~...
),因为-p
参数就表示对目标文件的每行应用这段程序。同样,因为我用来/g
这个修饰符,就可以保证在一行文本中可以进行多次替换。尽管在这里我只对一个文件进行操作,但也很容易在命令行中列出多个文件,而Perl会把替换命令引用到每个文件中的每一行文字。这样只需一条简单的命令,我就能够编辑大量的文件。这样简单的编辑方式是Perl独有的,但是这个例子告诉我们,即使执行的是简单的任务,作为脚本语言一部分的正则表达式的功能依然非常强大。
实例验证,我这里替换个文件试试看就行了。
public class TestReplace
{
public String str1="窗前明月光,";
public String str1="疑似地上霜.";
public String str1="举头望明月,";
public String str1="低头思故乡.";
public String toString()
{
return "["+str1+str2+str3+str3+"]";
}
public static void main(String[] args)
{
System.out.println(new TestReplace());
}
}
执行命令:perl -p -i -e 's/public/private/g' replaceAll.java
把replaceAll.java
中的所有public
关键字全部替换成private
关键字。当然这仅仅作为一个例子,与Java没什么关系。
替换后的replaceAll.java
如下
private class TestReplace
{
private String str1="窗前明月光,";
private String str1="疑似地上霜.";
private String str1="举头望明月,";
private String str1="低头思故乡.";
private String toString()
{
return "["+str1+str2+str3+str3+"]";
}
private static void main(String[] args)
{
System.out.println(new TestReplace());
}
}