正则表达式的基础语法
点“.”
我们第一个要讲解的元字符是“.”。这个符号意味着可以匹配任意一个字符。如下所示的正则表达式:c.t意味着匹配“以c开头,之后是任意一个字符,紧跟着是字母t”的字符串。在一段文本中,这样的正则表达式可以用来找出cat, cot, czt这样的字符串,甚至可以找出c.t这样的组合,但是不能找到ct或者是coot这样的字符串。
反斜杠“\”
使用反斜杠“\”可以忽略元字符,使得元字符的功能与普通字符一样。所以,正则表达式c\.t表示“找到字母c,然后是一个句号(“.”),紧跟着字母t”反斜杠本身也是一个元字符,这意味着反斜杠本身也可以通过相似的方法变回到普通字符的用途。因此,正则表达式c\\t表示匹配“以字符c开头,然后是一个反斜杠,紧跟着是字母t”的字符串。
字符类
字符类是一组在方括号内的字符,表示可以匹配其中的任何一个字符。
正则表达式c[aeiou]t,表示可以匹配的字符串是”以c开头,接着是aeiou中的任何一个字符,最后以t结尾”。在文本的实际应用中,这样的正则表达式可以匹配:cat,cet,cit,cot,cut五种字符串。正则表达式[0123456789]表示匹配任意一个整数。正则表达式[a]表示匹配单字符a。
重要提示:字符类中和字符类外的规则有时不同,一些字符在字符类中是元字符,在字符类外是普通字符。一些字符正好相反。还有一些字符在字符类中和字符类外都是元字符,这要视情况而定!比如,.表示匹配任意一个字符,而[.]表示匹配一个全角句号。这不是一回事!
字符类的范围
在字符集中,你可以通过使用短横线来表示匹配字母或数字的范围。
[b-f]与[b,c,d,e,f]相同,都是匹配一个字符”b”或”c”或”d”或”e”或”f”
[A-Z]与[ABCDEFGHIJKLMNOPQRSTUVWXYZ]相同,都是匹配任意一个大写字母。
[1-9]与[123456789]相同,都是匹配任意一个非零数字。
下文中,我们会讲解,怎样有效缩短这样的正则表达式长度。
在字符类之外,短横线没有特殊含义。正则表达式a-z,表示匹配字符串“以a开头,然后是一个短横线,以z结尾”。
范围和单独的字符可能在一个字符类中同时出现:
[0-9.,]表明匹配一个数字,或者一个全角句号,或者一个逗号
[0-9a-fA-F]意味着匹配一个十六进制数
[a-zA-Z0-9\-]意味着匹配一个字母、数字或者一个短横线
练习:
使用已经介绍过的正则表达式知识,匹配YYYY-MM-DD格式的日期。
答案:
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].
同样的,下文中,我们会介绍怎样有效减少这样的正则表达式长度。
虽然你可以尝试在正则表达式中使用一些非字母或数字作为范围的最后一个符号,比如abc[!-/]def,但是这并不是在每种实现中都合法。即使这样的语法是合法的,这样的语义也是模糊的。最好不要这样使用。同时,你必须谨慎选择范围的边界值。即使[A-z]在你使用的实现中,是合法的,也可能会产生无法预料的运行结果。(注意,在z到a之间,是有字符存在的)
注意:范围的字符值代表的是字符而已,并不能代表数值范围,比如[1-31]表示匹配一个数字,是1或者2或者3,而不是匹配一个数值在1到31之间的数。
字符类的反义
你可以在字符类的起始位放一个反义符。
[^a]表示匹配任何不是“a”的字符
[^a-zA-Z0-9]表示匹配任何不是字母也不是数字的字符
[\^abc]匹配一个为“^”或者a或者b或者c的字符
[^\^]表示匹配任何不为“^”的字符
转义字符类
\d这个正则表达式与[0-9]作用相同,都是匹配任何一个数字。(要匹配\d,应该使用正则表达式\\d)
\w与[0-9A-Za-z]相同,都表示匹配一个数字或字母字符
\s意味着匹配一个空字符(空格,制表符,回车或者换行)
另外,
\D与[^0-9]相同,表示匹配一个非数字字符。
\W与[^0-9A-Za-z]相同,表示匹配一个非数字同时不是字母的字符。
\S表示匹配一个非空字符。
这些是你必须掌握的字符。你可能已经注意到了,一个全角句号“.”也是一个字符类,可以匹配任意一个字符。
很多正则表达式的实现中,提供了更多的字符类,或者是标志位在ASCII码的基础上,扩展现有的字符类。
特别提示:统一字符集中包含除了0至9之外的更多数字字符,同样的,也包含更多的空字符和字母字符。实际使用正则表达式时,请仔细查看相关文档。
练习:
简化正则表达式 [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9].
答案:
\d\d\d\d-\d\d-\d\d.
重复
在字符或字符集之后,你可以使用{ }大括号来表示重复
正则表达式a{1}与a意思相同,都表示匹配字母a
a{3}表示匹配字符串“aaa”
a{0}表示匹配空字符串。从这个正则表达式本身来看,它毫无意义。如果你对任何文本执行这样的正则表达式,你可以定位到搜索的起始位置,即使文本为空。
a\{2\}表示匹配字符串“a{2}”
在字符类中,大括号没有特殊含义。[{}]表示匹配一个左边的大括号,或者一个右边的大括号
练习:
简化下面的正则表达式
z.......z
\d\d\d\d-\d\d-\d\d
[aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]
[bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]
答案:
z.{7}z
\d{4}-\d{2}-\d{2}
[aeiou]{6}
[bcdfghjklmnpqrstvwxyz]{10}
注意:重复字符是没有记忆性的,比如[abc]{2}表示先匹配”a或者b或者c”,再匹配”a或者b或者c”,与匹配”aa或者ab或者ac或者ba或者bb或者bc或者ca或者cb或者cc“一样。[abc]{2}并不能表示匹配”aa或者bb或者cc“
指定重复次数范围
重复次数是可以指定范围的
x{4,4}与x{4}相同
colou{0,1}r表示匹配colour或者color
a{3,5}表示匹配aaaaa或者aaaa或者aaa
注意这样的正则表达式会优先匹配最长字符串,比如输入 I had an aaaaawful day会匹配单词aaaaawful中的aaaaa,而不会匹配其中的aaa。
重复次数是可以有范围的,但是有时候这样的方法也不能找到最佳答案。如果你的输入文本是I had an aaawful daaaaay那么在第一次匹配时,只能找到aaawful,只有再次执行匹配时才能找到daaaaay中的aaaaa.
重复次数的范围可以是开区间
a{1,}表示匹配一个或一个以上的连续字符a。依然是匹配最长字符串。当找到第一个a之后,正则表达式会尝试匹配尽量多个的连续字母a。
.{0,}表示匹配任意内容。无论你输入的文本是什么,即使是一个空字符串,这个正则表达式都会成功匹配全文并返回结果。
练习:
使用正则表达式找到双引号。要求输入字符串可能包含任意个字符。
调整你的正则表达式使得在一对双引号中间不再包含其他的双引号。
答案:
".{0,}", 然后 "[^"]{0,}".
关于重复的转义字符
?与{0,1}相同,比如,colou?r表示匹配colour或者color
*与{0,}相同。比如,.*表示匹配任意内容
+与{1,}相同。比如,\w+表示匹配一个词。其中”一个词”表示由一个或一个以上的字符组成的字符串,比如_var或者AccountName1.
这些是你必须知道的常用转义字符,除此之外还有:
\?\*\+ 表示匹配字符串”?*+”
[?*+]表示匹配一个问号,或者一个*号,或者一个加号
练习:
简化下列的正则表达式:
".{0,}" and "[^"]{0,}"
x?x?x?
y*y*
z+z+z+z+
答案:
".*" and "[^"]*"
x{0,3}
y*
z{4,}
练习
写出正则表达式,寻找由非字母字符分隔的两个单词。如果是三个呢?六个呢?
\w+\W+\w+, \w+\W+\w+\W+\w+, \w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+.
下文中,我们将简化这个正则表达式。
非贪婪匹配
正则表达式 “.*” 表示匹配双引号,之后是任意内容,之后再匹配一个双引号。注意,其中匹配任意内容也可以是双引号。通常情况下,这并不是很有用。通过在句尾加上一个问号,可以使得字符串重复不再匹配最长字符。
\d{4,5}?表示匹配\d\d\d\d或者\d\d\d\d\d。也就是和\d{4}一样
colou??r与colou{0,1}r相同,表示找到color或者colour。这与colou?r一样。
“.*?”表示先匹配一个双引号,然后匹配最少的字符,然后是一个双引号,与上面两个例子不同,这很有用。
选择匹配
你可以使用|来分隔可以匹配的不同选择:
cat|dog表示匹配”cat”或者”dog”
red|blue|以及red||blue以及|red|blue都表示匹配red或者blue或者一个空字符串
a|b|c与[abc]相同
cat|dog|\|表示匹配”cat”或者”dog”或者一个分隔符”|“
[cat|dog]表示匹配a或者c或者d或者g或者o或者t或者一个分隔符“|”
练习
简化下列正则表达式:
s|t|u|v|w
aa|ab|ba|bb
[abc]|[^abc]
[^ab]|[^bc]
[ab][ab][ab]?[ab]?
答案
[s-w]
[ab]{2}
.
[^b]
[ab]{2,4}
练习
使用正则表达式匹配1到31之间的整数,[1-31]不是正确答案!
这样的正则表达式不唯一. [1-9]|[12][0-9]|3[01] 是其中之一。
分组
你可以使用括号表示分组:
通过使用 Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day 匹配一周中的某一天
(\w*)ility 与 \w*ility 相同。都是匹配一个由”ility”结尾的单词。稍后我们会讲解,为何第一种方法更加有用。
\(\)表示匹配一对括号。
[()]表示匹配任意一个左括号或者一个右括号
练习:
在《时间机器中》找到一对括号中的内容,然后通过修改正则表达式,找到不含括号的内容。
答案
\(.*\). 然后是, \([^()]*\).
分组可以包括空字符串:
(red|blue)表示匹配red或者blue或者是一个空字符串
abc()def与abcdef相同
你也可以在分组的基础上使用重复:
(red|blue)?与(red|blue|)相同
\w+(\s+\w+)表示匹配一个或多个由空格分隔的单词
练习
简化正则表达式 \w+\W+\w+\W+\w+ 以及 \w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+.
答案:
\w+(\W+\w+){2}, \w+(\W+\w+){5}.
单词分隔符
在单词和非单词之间有单词分隔符。记住,一个单词\w是[0-9A-Za-z_],而非单词字符是\W(大写),表示[^0-9A-Za-z_].
在文本的开头和结尾通常也有单词分隔符。
在输入文本it’s a cat中,实际有八个单词分隔符。如果我们在cat之后在上一个空格,那就有九个单词分隔符。.
\b表示匹配一个单词分隔符
\b\w\w\w\b表示匹配一个三字母单词
a\ba表示匹配两个a中间有一个单词分隔符。这个正则表达式永远不会有匹配的字符,无论输入怎样的文本。
单词分隔符本身并不是字符。它们的宽度为0。下列正则表达式的作用不同
(\bcat)\b
(\bcat\b)
\b(cat)\b
\b(cat\b)
练习:
在词典中找到最长的单词。
答案
在尝试之后发现,\b.{45,}\b可以在字典中找到最长单词
其他建议
输入验证
正则表达式可以用来进行输入验证。但是严格的输入验证会使得用户体验较差。比如:
信用卡号
在一个网站上,我输入了我的卡号比如 1234 5678 8765 4321 网站拒绝接收。因为它使用了正则表达式\d{16}。
正则表达式应该考虑到用户输入的空格和短横线。
实际上,为什么不先过滤掉所有的非数字字符,然后再进行有效性验证呢?这样做,可以先使用\D以及空的替换表达式。
练习
在不先过滤掉所有的非数字字符的情况下,使用正则表达式验证卡号的正确性。
答案
\D*(\d\D*){16} is one of several variations which would accomplish this.
名字
不要使用正则表达式来验证姓名。实际上,即使可以,也不要企图验证姓名。
程序员对名字的错误看法:
名字中不含空格
名字中没有连接符号
名字中只会使用ASCII码字符
名字中出现的字都在特殊字符集中
名字至少要有M个字的长度
名字不会超过N个字的长度
人们只有一个名
人们只有一个中间名
人们只有一个姓(最后三条是从英语的人名考虑)
电子邮件地址
不要使用正则表达式验证邮箱地址的正确性。
首先,这样的验证很难是精确的。电子邮件地址是可以用正则表达式验证的,但是表达式会非常的长并且复杂。
短的正则表达式会导致错误。(你知道吗?电子邮箱地址中会有一些注释)
第二,即使一个电子邮件地址可以成功匹配正则表达式,也不代表这个邮箱实际存在。邮箱的唯一验证方法,是发送验证邮件。
注意:
在严格的应用场景中,不要使用正则表达式来解析HTML或者XML。解析HTML或者XML:
使用简单的正则表达式不能完成
总体来说非常困难
已经有其他的方法解决
找到一个已经有的解析库来完成这个工作