这样的正则表达式不唯一.[1-9]|[12][0-9]|3[01]
是其中之一。
在单词和非单词之间有单词分隔符。记住,一个单词\w是[0-9A-Za-z_],而非单词字符是\W(大写),表示[^0-9A-Za-z_].
在输入文本it’s a cat中,实际有八个单词分隔符。如果我们在cat之后在上一个空格,那就有九个单词分隔符。.
练习
使用正则表达式在《时间机器》中找到最长的一行。
答案
使用正则表达式^.{73,}$可以匹配长度为73的一行
文本分界
在很多的正则表达式实现中,将^和$作为文本的开始符号和结束符号。
还有一些实现中,用\A和\z作为文本的开始和结束符号。
捕捉和替换
从这里开始,正则表达式真正体现出了它的强大。
捕获组
你已经知道了使用括号可以匹配一组符号。使用括号也可以捕获子串。假设正则表达式是一个小型计算机程序,那么捕获子串就是它输出的一部分。
正则表达式(\w*)ility表示匹配以ility结尾的词。第一个被捕获的部分是由\w*控制的。比如,输入的文本内容中有单词accessibility,那么首先被捕获的部分是accessib。如果输入的文本中有单独的ility,则首先被捕获的是一个空字符串。
你可能会有很多的捕获字符串,它们可能靠得很近。捕获组从左向右编号。也就是只需要对左括号计数。
假设有这样的正则表达式:(\w+) had a ((\w+) \w+)
输入的内容是:I had a nice day
- 捕获组1:I
- 捕获组2:nice day
- 捕获组3:nice
在一些正则表达式的实现中,你可以从零开始编号,编号零表示匹配整句话:I had a nice day
.
在其他的实现中,如果没有制定捕获组,那么捕获组1会自动地填入捕获组0的信息。
是的,这也意味着会有很多的括号。有一些正则表达式的实现中,提供了“非捕获组”的语法,但是这样的语法并不是标准语法,因此我们不会介绍。
从一个成功的匹配中返回的捕获组个数,与使用原来的正则表达式获得的捕获组个数相同。记住这一点,你可以解释一些奇怪的现象。.
正则表达式((cat)|dog)表示匹配cat或者dog。这里有两个捕获组,如果输入文本是dog,那么捕获组1是dog,捕获组2为空。
正则表达式a(\w)*表示匹配一个以a开头的单词。这里只有一个捕获组
- 如果输入文本为a,捕获组1为空。
- 如果输入文本为ad,捕获组为d
- 如果输入文本为avocado,捕获组1为v。但是捕获组0表示整个单词avocado.
替换
假如你使用了一个正则表达式去匹配字符串,你可以描述另外一个字符串来替换其中的匹配字符。用来替换的字符串称为替换表达式。它的功能类似于
- 常规的Replace会话
- Java中的String.replace()函数
- PHP的str_replace()函数
- 等等
练习
将《时间机器》中所有的元音字母替换为r。
答案
使用正则表达式[aeIoU]以及[AEIoU],对应的替换字符串分别为r,R.
但是,你可以在替换表达式中引用捕获组。这是在替换表达式中,你可以唯一操作的地方。这也是非常有效的,因为这样你就不用重构你找到的字符串。
假设你正在尝试将美国风格的日期表示MM/DD/YY替换为ISO 8601日期表示YYYY-MM-DD
- 从正则表达式(\d\d)/(\d\d)/(\d\d)开始。注意,这其中有三个捕获组:月份,日期和两位的年份。
- .捕获组的内容和捕获组编号之间用反斜杠分隔,因此你的替换表达式应该是
20\3-\1-\2
.
- 如果我们输入的文本中包含03/04/05表示2005年3月4日那么:
- 捕获组1:03
- 捕获组2:04
- 捕获组3:05
- 替换字符串
2005-03-04
.
在替换表达式中,你可以多次使用捕获组
- 对于双元音,正则表达式为([aeIoU]),替换表达式为\l\l
- 在替换表达式中不能使用反斜杠。比如,你在计算机程序中希望使用字符串中使用部分文本。那么,你必须在每个双引号或者反斜杠之前加上反斜杠。
- 你的正则表达式可以是([\\"])。捕获组1是双引号或者反斜杠
- 你的替换表达式应该是\\\l
练习
使用正则表达式和替换表达式,将23h59这样的时间戳转化为23:59.
答案
正则表达式finds the timestamps,替换表达式\1:\2
反向引用
在一个正则表达式中,你也可以引用捕获组。这称作:反向引用
比如,[abc]{2}表示匹配aa或者ab或者ac或者ba或者bb或者bc或者ca或者cb或者cc.但是{[abc]}\1表示只匹配aa或者bb或者cc.
练习
在字典中,找到包含两次重复子串的最长单词,比如papa
,monospace">coco
\b(.{6,})\1\b
匹配chiquichiqui
.
如果我们不在乎单词的完整性,我们可以忽略单词的分解,使用正则表达式(.{7,})\1
匹配
countercountermeasure
以及countercountermeasures
.
使用正则表达式编程
特别提醒:
过度使用的反斜杠
在一些编程语言,比如Java中,对于包含正则表达式的字符串没有特殊标记。字符串有着自己的过滤规则,这是优先于正则表达式规则的,这是频繁使用反斜杠的原因。
比如在Java中
- 匹配一个数字,使用的正则表达式从\d变为代码中的String re= “\\d”
- 匹配双引号字符串的正则表达式从
"[^"]*"
变为String re = “\”[^\"]*\”"
- 匹配反斜杠或者是左边方括号,或者右边方括号的正则表达式从[\\
\[\]
]变为String re = “[\\\\\
]”;
String re = "\\s";
和String re = "[ \t\r\n]";
是等价的. 注意它们实际执行调用时的层次不同。
在其他的编程语言中,正则表达式是由特殊标明的,比如使用/。下面是JavaScript的例子:
- 匹配一个数字,\d会简单写成
var regExp = /\d/;
.
- 匹配一个反斜杠或者一个左边的方括号或者一个右边的方括号,
var regExp = /[\\\[\]
]/;
var regExp = /\s/;
和var regExp = /[ \t\r\n]/;
是等价的
- 当然,这意味着在使用/时必须重复两次。比如找到URL必须使用
var regExp = /https?:\/\//;
.
我希望现在你能明白,我为什么让你特别注意反斜杠。
动态正则表达式
当你动态创建一个正则表达式的时候请特别小心。如果你使用的字符串不够完善的花,可能会有意想不到的匹配结果。这可能导致语法错误,更糟糕的是,你的正则表达式语法正确,但是结果无法预料。
错误的Java代码:
String sep = System.getProperty(“file.separator”); String[] directories = filePath.split(sep);
Bug:String.split()认为sep是一个正则表达式。但是,在Windows中,Sep是表示匹配一个反斜杠,也就是与正则表达式”\\”相同。这个正则表达式是正确的,但是会返回一个异常:PatternSyntaxException
.
任何好的编程语言都会提供一种良好的机制来跳过字符串中所有的元字符。在Java中,你可以这样实现:
String sep = System.getProperty(“file.separator”);
String[] directories = filePath.split(Pattern.quote(sep));
循环中的正则表达式
将正则表达式字符串加入反复运行的程序中,是一种开销很大的操作。如果你可以在循环中避免使用正则表达式,你可以大大提高效率。
其他建议
输入验证
正则表达式可以用来进行输入验证。但是严格的输入验证会使得
用户体验较差。比如:
信用卡号
在一个网站上,我输入了我的卡号比如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:
- 使用简单的正则表达式不能完成
- 总体来说非常困难
- 已经有其他的方法解决
找到一个已经有的解析库来完成这个工作
这就是55分钟的全部内容
总结:
- 字符:
a
b
c
d
1
2
3
4
etc.
- 字符类:
.
[abc]
[a-z]
\d
\w
\s
.
代表任何字符
表示
“数字”
\w
表示”字母”,monospace">[0-9A-Za-z_]
\s
表示 “空格,制表符,回车或换行符”
- 否定字符类:
[^abc]
\D
\W
\S
- 重复:
{4}
{3,16}
{1,}
?
*
+
?
表示 “零次或一次”
*
表示 “大于零次”
+
表示 “一次或一次以上”
- 如果不加上?,所有的重复都是最长匹配的(贪婪)
- 分组:
(Septem|Octo|Novem|Decem)ber
- 词,行以及文本的分隔:
\b
^
$
\A
\z
- 转义字符:
\1
\2
\3
etc. (在匹配表达式和替换表达式中都可用)
- 元字符:
\
[
]
{
}
+
|
(
)
$
- 在字符类中使用元字符:
-
^
- 使用反斜杠可以忽略元字符:
\
致谢
正则表达式非常常用而且非常有用。每个人在编辑文本或是编写程序时都必须了解怎样使用正则表达式。
练习
选择正则表达式的某种实现,阅读相关文档。我保证,你会学到更多。