Regular Expressions
Ashley J.S Mills
<ashley@ashleymills.com>
Copyright © 2005 The University Of Birmingham
Sayid.YoungTranslates
1 介绍
模式匹配是计算机科学的一门重要学问,它主要研究对信息的定义和按照定义进行匹配识别。人类每天都在使用各种模式(已经定义的信息),去识别各种事物。计算机也在使用模式作些非常基础的处理,当你在命令行的终端模式下执行一条命令,计算机使用一些已定义好的模式来发现你的命令,并且知道你让它去做什么。模式匹配也会用在计算机编程语言的编译过程中。
正则表达式是一种重要的模式匹配方式,用在正则语言中。正则语言是模式匹配语言的一个分支。它用在不是很复杂的机械化语言中非常奏效。正则表达式听起来陌生,但是在你不经意中,你却经常在用它。例如,当你想删除所有文件的时候,你在命令行敲下了“delete *.*”,那么你已经使用了正则表达式。类似的,很多情况下,你被告知:“*”被用来代替任何东西,这意味着,任何东西都会被匹配上,事实上,在后面讨论正则表达式的时候,也有类似的用法。
现实中,有很多程序都是可以通过正则表达式来完成,实现功能目标。很多情况下,我们看到,多语义上,虽然都在发生轻微变化,但是,幸好,他们在概念层次上会描述出清晰的边界,这样的语义文本我们可以用来做讨论,并且从中找到正则表达式能够使用的广义规则,这些广义规则可以在将来适当的时候使用。
2 基础
正则表达包括两种字符,常字符(literal characters)和元字符(meta characters)。常字符,是你想要发现的;元字符是你用来做区分的。常字符容易理解,元字符,是一些特殊字符,象“*”号这样的,它是正则表达式的核心概念,因此我们会在下文做详细的介绍,并且还要详细阐述更多的这样的元字符。
2.1 单字符
一个单字符,例如Q是一个正则表达式,这个正则表达式会匹配上任何包含有Q的字符串,所以它会匹配上Quick,Quiet 以及Quantum,但是匹配不上quick(仔细想想原因)。
2.2 任何字符
英语中的实心句号,在进行搜索的时候,会用作替代任何字符。例如,“.t.m”会匹配上tom, tem和stem 以及其他,类似的词。一个很好的例子是,可以用这种方式利用网上的搜索引擎帮你完成填字游戏的任务。(不过注意,有些时候”.” 会被“?”取代)
2.3 反斜线”\”
反斜线“\”时常被用来把一个元字符转义成平字符。在特殊时候,也是反过来的。一个被转义的字符要跟随转义字符后面。例如“\*”会被包含”*” 的字符串匹配上。
转义经常会发生,这时“\”就用来标识一个被用来做为常字符的元字符。例如,在强调重点时,人们会用双引号把一段字符串包围起来。而双引号往往被当作元字符,这个时候就需要转义。在你使用正则表达式的时候,你需要阅读一下你使用的正则表达式的版本,去查看关于转义部分的实现。
2.4 ^字符
“^”,它的头尖尖的,有明显的插入形象的含义。正像它的形象含义,它表示一个字符串的开始,例如“^CAPITAL” , 那么以”CAPITAL”开始的句子会被匹配上,而“Your such a CAPITAL idiot!”不会被匹配上。
2.5 美元符$
美元符$用来匹配一个字符串的最后几个字符,例如“here$”会匹配上”I like it here” 但是不会匹配上”here is a potato”。
2.6 星号符*
星号符”*” ,在正则表达式中,指会重复替代前一个正则表达式零或任意多次。例如“10*”会匹配“1”,“10”,“100”,“1000”等等。
2.7 加号+
星号符”+” ,在正则表达式中,指会重复替代前一个正则表达式至少一次以上。例如“10*”会匹配“10”,“100”,“1000”等等,但不会与“1”匹配上。
注意:
(regular expression)+ 与(regular expression)(regular expression)*相当
2.8 区间:[], [cn-cm]以及[^cn-cm]
[]是一个以枚举方式描述的模式集合,一个字符串会被匹配上,那么它的某段文字肯定可以和[]中的任何字符或正则表达式匹配上。例如:1[123]512会被”11512” , ”12512” 以及”13512” 匹配上。
[cn-cm] 用来明确一个字符集的范围(在此范围内),一个字符串被匹配上,那么他的某个字符,肯定是在[cn-cm]范围内。例如:“[b-f]oo”,以下字符串会被匹配上:“boo, ”coo”, ”doo”, “eoo”和 “foo” 但是”goo”不会被匹配上。
[^cn-cm] 表示[cn-cm]区域以外的才能够匹配。注意,当^在[之后,它有特殊的正则表达式含义,因此在[]范围内的^要想作为原先含义使用必须用转义符,象这个样子”[\^]”或“[^\^]”。“[^1-8]00”只有“900”可以被匹配上,而其他的都无法匹配上。
2.9 分组: “\(\)”
“\(\)”用来在正则表达式中将包含在括号里的表达式定义成组。定义成组后,组的正则表达式就可以被后面的内容引用了。例如\1会引用第一个组定义的正则表达式。分组的语义在正则表达式的不同版本中的实现是不一样的,有些不需要对括号进行转义;而有些会使用不同的规则去引用定义的组,例如一些版本的程序用“$1”去引用前面的组(像上面的“\1”的用能一样)。有些会对组的数量加以限制,录入最多不能超过9个组被引用。
例子:在grep程序中grep “\(a\)b\1” ,字符串”aba” 会被匹配上。
2.10 选择符“|”
“|”被用来当作OR分隔符。在使用“|”选择符时,它两侧的正则表达式,任何一个如果使字符串满足匹配条件,该字符串都会被匹配上。例如:“^aba|b$”那么下面的字符串都会被匹配上,”aba” , ”abb”,但是”abc”不会被匹配上。“|”元字符不需要被转义。
2.11 重复符:\{n\}, \{,n\}, \{n,\}, \{n, m\}
\{n\} 用来表达它之前的正则表达式需要被匹配n次。例如“^10\{3\}$”,下面字符串会被匹配上,“1000”但是“100”和“10000”都不会被匹配上。
\{,n\}用来表示它之前的正则表达式最多要被匹配上n次。接着上面的例子,那么下面的字符串会被匹配上,“1”,“100”,“1000”,但是“10000”仍然不会被匹配上。
\{n,\}用来表示它之前的正则表达式最少要被匹配上n次。同样接着上面的例子,那么下面的字符串会被匹配上,”1000”,“10000”但是“1”,“100”都不会被匹配上。
\{n,m\} 是上面语法的交集。例如“^10\{3,4\}$”,那么能匹配上的字符串是“1000”和“10000”。
注:
星号“*”和加号“+”有时候在你使用的正则表达式的版本中不支持,那么\{0,\}和\{1,\}分别对应“*”,“+”的语法含义。
3 Grep 例子
4 java.util.regex,Java 1.4
java.util.regex 提供了正则表达式的字符串匹配。在java.util.regex包中,有两个比较常用的类Matcher和Pattern。Pattern 在java中提供了正则表达式的功能。Matcher 提供了如何使用Pattern做字符串匹配的方法。Java.util.regex的api可以在java API1.4中找到(http://java.sun.com/j2se/1.4/docs/api/java/util/regex/package-summary.html)。
因此,可以通过一个程序看看java 正则表达式的功能。我们开发了一个程序,使用了两个参数searchPattern和searchString,然后打印一些在使用searchPattern去匹配searchString时的一些显而易见的信息。这个测试程序通过传入参数向searchPattern和searchString进行赋值,从而避免了对这个测试程序的重新编译,但同是可以完美地展示java正则表达式的能力,兼容性,以及如何使用java.util.regex构建应用。程序代码如下:
import java.util.regex.*;
public class Regex {
public static voidmain(String args[]) {
String searchString= "",
searchPattern ="";
if(args.length==2) {
searchPattern =args[0];
searchString =args[1];
} else {
output("Usage:");
output("javaregex searchPattern searchString");
System.exit(0);
}
Pattern p =Pattern.compile(searchPattern);
Matcher m =p.matcher(searchString);
boolean b =m.find();
output("\nMatchfound : "+b);
while(b) {
output("Matchstart : " + m.start());
output("Matchend : " + m.end());
output("Matchcontent : " + m.group(0));
if(m.groupCount()!=0){
for(int i=1;i<=m.groupCount(); i++) {
output("Group" + i + " : " + m.group(i));
}
}
b = m.find();
if(b)output("\nMatch found : "+b);
}
}
private static voidoutput(String s) {
System.out.println(s);
}
}
程序开始时引入了java 正则表达包 java.util.regex。字符串变量,searchPattern 和 searchString 用来准备下一步使用。程序判断传入参数是否是两个,如果不是,就返回并且输出”usage ......”。如果传入的参数是两个,那么第一个参数赋值给searchPattern第二个参数赋值给searchString。
Pattern p = Pattern.compile(searchPattern);
Matcher m = p.matcher(searchString);
Boolean b = m.find();
Pattern对象由compile方法创建,创建时使用searchPattern字符串。compile方法将searchPattern视为正则表达式,并将表达式编译成pattern对象。Matcher对象由当前的Pattern对象的matcher方法创建,创建时matcher方法使用searchString作为参数。m是Matcher对象,m.find()返回一个boolean值b,用来表示Matcher对象m在searchString中下一次匹配上pattern对象p的真值(匹配上为true)。当Mather对象是successful状态,它就包含了一些关于匹配的信息,例如匹配的位置,以及内容中标记的group。
output("\nMatchfound : "+b);
while(b) {
output("Match start : " + m.start());
output("Match end : " + m.end());
output("Match content : " + m.group(0));
if(m.groupCount()!=0) {
for(int i=1; i<=m.groupCount(); i++) {
output("Group " + i + " : " +m.group(i));
}
}
b = m.find();
if(b)
output("\nMatch found : "+b);
第一行,在循环的外部,打印了是否匹配上了。如果匹配上了,循环才开始。在循环的内部
用Matcher对象的start()方法打印匹配的开始位置,用end()方法打印匹配的结束位置(+1)。如果表达式中有分组,那么被分组模式匹配上的字符串片段使用group(int i)打印出来,这个函数返回第i个分组在searchString中被匹配上的字符串片断,它非常象小括号内标注的组。Group(0) 返回全部匹配上的模式。
如果存在被定义在pattern中的分组被匹配上,这部分循环代码的打印输出分组匹配的内容,group(0)没有打印输出,因为早已经打印了(在循环的外面,group(0)是完全匹配的结果)。boolean值b被设置成下一个find()的结果,这意味着如果他是真,则下一个模式被匹配上了。程序打印出已经发现了一次匹配。这个条件是必须的,因此在最后全部被匹配上后,“Match found: false”不能打印出来。
程序执行结构如下:
javaRegex patternString searchString
Here are a few examples:
javaRegex "Hello" "Hello World!"
Match found : true
Match start : 0
Regular Expressions
6
Match end : 5
Match content :Hello
javaRegex "[Hh]ello" "Hello there Peter! Oh hello there James!"
Match found : true
Match start : 0
Match end : 5
Match content :Hello
Match found : true
Match start : 22
Match end : 27
Match content :hello
javaRegex "(H)(e)(l)(l)(o)" "Hello ello ello!"
Match found : true
Match start : 0
Match end : 5
Match content :Hello
Group 1 : H
Group 2 : e
Group 3 : l
Group 4 : l
Group 5 : o
javaRegex "H(e(l(l(o))))" "Hello ello ello"
Match found : true
Match start : 0
Match end : 5
Match content :Hello
Group 1 : ello
Group 2 : llo
Group 3 : lo
Group 4 : o
javaRegex "!*" "0+ !!!"
Match found : true
Match start : 0
Match end : 0
Match content :
Match found : true
Match start : 1
Match end : 1
Match content :
Match found : true
Match start : 2
Match end : 2
Match content :
Match found : true
Match start : 3
Match end : 6
Match content : !!!
Match found : true
Match start : 6
Match end : 6
Match content :
当我们看到这个例子的输出时,会觉得很奇怪,为什么会有空的内容被匹配上,还有一个或多个“!”被匹配上?不过他向我们演示了输入字符串中的每个字符如何使用模式进行匹配。在这个例子中正则表达式的语义描述的是在文档内容中匹配零个或以上的“!”。因此会有空内容被匹配上,还有一个,两个,甚至三个“!”被匹配上的情况。
javaRegex "S.*t" "Spontaneous combustion"
Match found : true
Match start : 0
Match end : 19
Regular Expressions
7
Match content :Spontaneous combust
javaRegex "S.*?t" "Spontaneous combustion"
Match found : true
Match start : 0
Match end : 5
Match content : Spont
与上一例子不同的是,第一个表达式中使用了“.*”,这种表达式导致任何的字符都可以被匹配上,这种匹配会持续到下一个表达式被匹配上。在这个例子中,下一个表达式是?,“.*?”是一个少见的用法,他会使匹配继续进行下去,继续到下一个表达式匹配发生。