由于各种原因,程序员们经常要对多个源代码文件进行有规则的批量修改,大多数情况下,这种修改都是手工完成的。手工进行这种批量修改,费时费力而且还枯燥无味。既然是有规则的修改,是不是可以自动完成呢?

如果是对固定字符串的替换,使用大部分编辑器和集成开发环境提供的“搜索并替换”功能就可以完成任务了。如果对变量,方法,类等进行改名操作,一些集成开发环境(如Visual Studio 2005/2008,Eclipse等)提供的重构功能可以安全地达到目的。而遇到复杂一点的情形,用上述的方法就行不通了。

本文要介绍的方法是结合正则表达式的强大搜索替换能力和Perl便利的文本处理能力来进行比较复杂规则的批量文件修改。使用这种方法,不需要学习Perl语言,因为只需要用Perl命令行就够了,当然如果懂Perl的话更好。虽然不用学习Perl,但正则表达式却是必须会用的,本文就不详细介绍正则表达式了,网上可以找到很好的教程。

从一个简单的例子开始,先看一段C#代码:

using System;
using System.Collections.Generic;
namespace MyTest { public class TestNullable { public string Name { get; private set; }
public Nullable<DateTime> Birthday { get; set; }
public Nullable<int> Age { get { Nullable<int> age = null;
if (Birthday != null) { age = DateTime.Now.Year - Birthday.Value.Year; } return age; } }
public Nullable<bool> Sex { get; set; }
public IDictionary<Nullable<Guid>, Nullable<int>> Records { get; private set; }
public TestNullable(string name) { Name = name; Records = new Dictionary<Nullable<Guid>, Nullable<int>>(); } } }

这段代码中的,Nullable数据类型都是用“Nullable<值类型>”的形式声明的。如果有一天,上司要求统一规范,所有的Nullable数据类型都必须以值类型后跟一个“?”号的形式来声明,比如“Nullable<int>”改为“int?”,怎么办呢?别急,只用如下一条简单的命令行就可以完成转换了:

perl –p -i.bak -e "s/Nullable<(\w+)>/\1?/g" TestNullable.cs

转换结果如下:

using System;
using System.Collections.Generic;
namespace MyTest { public class TestNullable { public string Name { get; private set; }
public DateTime? Birthday { get; set; }
public int? Age { get { int? age = null;
if (Birthday != null) { age = DateTime.Now.Year - Birthday.Value.Year; } return age; } }
public bool? Sex { get; set; }
public IDictionary<Guid?, int?> Records { get; private set; }
public TestNullable(string name) { Name = name; Records = new Dictionary<Guid?, int?>(); } } }

酷吧,再看一下这个神奇的命令:

perl –p -i.bak -e "s/Nullable<(\w+)>/\1?/g" TestNullable.cs

perl是Perl语言的解释器,一般Linux操作系统下都是默认安装的,Windows系统下可以从ActiveState下载ActivePerl并安装。

命令行中双引号中的内容是一个正则表达式,“s/搜索/替换/参数”格式用于搜索和替换。参数中的g表示全局替换,如果不指定g的话,每次搜索替换过程只会替换第一处(上例的perl命令行每次替换过程处理文件中的一行,所以若不指定g的话便只有每行的第一处匹配会被替换)。搜索部分值得一提的是(\w+)\w可匹配字母数字或下划线,+号表示匹配一个或多个,括号表示捕获其包含的内容。替换中的\1表示第一组括号所捕获的内容。

命令行中最后的一个参数是要修改的文件名。可以指定多个文件名,用空格隔开。在linux环境下,由于一般的shell(如bash)都会自动展开通配符对应的文件名,所以可以直接使用类似*.cs的字符串来匹配多个文件。在Windows的命令行提示符中,尝试直接使用*.cs来匹配多个文件的话,会提示打不开文件的错误信息。不过使用Windows命令行技巧同样可以轻松利用通配符来批量处理多个文件:

FOR %A IN (*.cs) DO perl –p -i.bak -e "s/Nullable<(\w+)>/\1?/g" "%A"

更多细节请参见笔者的文章:使用Windows命令行模式进行文件批量处理(一)使用Windows命令行模式进行文件批量处理(二)

命令行参数-i.bak中的.bak指定为被修改的文件创建备份文件,备份文件的文件名是在原文件名之后加上“.bak”。如果单独使用-i而不带“.bak”,将不会创建备份文件。强烈建议每次都创建备份文件。(注意:我在Windows下使用ActivePerl 5.10版本测试时提示不允许不创建备份文件进行替换,而Ubuntu Linux下的perl 5.10是可以的。)

如果不想深究perl解释器是如何去处理我们指定的每个参数的,只需要每次把双引号中的正则表达式和对应的文件名修改掉就可以实现大部分的批量修改工作了。

本节中介绍了基本的perl单行程序正则表达式替换方法,下节将解释-p –i -e等perl解释器参数的细节并展示一些更复杂的应用实例。

标签:,