《精通正则表达式》第三版 2-2 使用正则表达式修改文本实例

使用正则表达式替换文本$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这个文本,所以执行之后的$varJEFF·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.javareplaceAll.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());
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值