原文章地址:

EZLippi/practical-programming-books​github.com

推荐几个正则表达式编辑器

  • Debuggex :https://www.debuggex.com/
  • PyRegex:http://www.pyregex.com/
  • Regexper:http://www.regexper.com/

正则表达式是一种查找以及字符串替换操作。正则表达式在文本编辑器中广泛使用,比如正则表达式被用于:

  • 检查文本中是否含有指定的特征词
  • 找出文中匹配特征词的位置
  • 从文本中提取信息,比如:字符串的子串
  • 修改文本

与文本编辑器相似,几乎所有的高级编程语言都支持正则表达式。在这样的语境下,“文本”也就是一个字符串,可以执行的操作都是类似的。一些编程语言(比如Perl,JavaScript)会检查正则表达式的语法。

正则表达式是什么?

正则表达式只是一个字符串。没有长度限制,但是,这样的正则表达式长度往往较短。如下所示是一些正则表达式的例子:

I had a S+ day today[A-Za-z0-9-_]{3,16}dddd-dd-ddv(d+)(.d+)*TotalMessages="(.*?)"<[^<>]>

这些字符串实际上都是微型计算机程序。正则表达式的语法,实际上是一种轻量级、简洁、适用于特定领域的编程语言。记住这一点,那么你就很容易理解下面的事情:

  • 每一个正则表达式,都可以分解为一个指令序列,比如“先找到这样的字符,再找到那样的字符,再从中找到一个字符。。。”
  • 每一个正则表达式都有输入(文本)和输出(匹配规则的输出,有时是修改后的文本)
  • 正则表达式有可能出现语法错误——不是所有的字符串都是正则表达式
  • 正则表达式语法很有个性,也可以说很恐怖
  • 有时可以通过编译,使得正则表达式执行更快

在实现中,正则表达式还有其他的特点。本文将重点讨论正则表达式的核心语法,在几乎所有的正则表达式中都可以见到这些规则。
特别提示:正则表达式与文件通配语法无关,比如 *.xml
正则表达式的基础语法
字符
正则表达式中包含了一系列的字符,这些字符只能匹配它们本身。有一些被称为“元字符”的特殊字符,可以匹配一些特殊规则。
如下所示的例子中,我用红色标出了元字符。

I had a S+ day today[A-Za-z0-9-_]{3,16}dddd-dd-ddv(d+)(.d+)*TotalMessages="(.*?)"<[^<>]*>

大部分的字符,包括所有的字母和数字字符,是普通字符。也就意味着,它们只能匹配它们自己,如下所示的正则表达式:
cat
意味着,只能匹配一个字符串,以“c”开头,然后是字符“a”,紧跟着是字符“t”的字符串。
到目前为止,正则表达式的功能类似于

String.indexOf()strpos()

注意:不做特殊说明,正则表达式中是区分大小写的。但是,几乎所有正则表达式的实现,都会提供一个Flag用来控制是否区分大小写。

catcotczt这样的字符串,甚至可以找出c.t这样的组合,但是不能找到ct或者是coot这样的字符串。
  • 正则表达式c[aeiou]t,表示可以匹配的字符串是”以c开头,接着是aeiou中的任何一个字符,最后以t结尾”。在文本的实际应用中,这样的正则表达式可以匹配:cat,cet,cit,cot,cut五种字符串。
  • 正则表达式[0123456789]表示匹配任意一个整数。
  • 正则表达式[a]表示匹配单字符a。

包含忽略字符的例子

[][]

在字符类中,字符的重复和出现顺序并不重要。[dabaaabcc]与[abc]是相同的
重要提示:字符类中和字符类外的规则有时不同,一些字符在字符类中是元字符,在字符类外是普通字符。一些字符正好相反。还有一些字符在字符类中和字符类外都是元字符,这要视情况而定!
比如,.表示匹配任意一个字符,而[.]表示匹配一个全角句号。这不是一回事!
字符类的范围
在字符集中,你可以通过使用短横线来表示匹配字母或数字的范围。

  • [b-f]与[b,c,d,e,f]相同,都是匹配一个字符”b”或”c”或”d”或”e”或”f”
  • [A-Z]与[ABCDEFGHIJKLMNOPQRSTUVWXYZ]相同,都是匹配任意一个大写字母。
  • [1-9]与[123456789]相同,都是匹配任意一个非零数字。
[aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]euouaeeuouaes同样的,恐怖的正则表达式[bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]sulphhydryls
  • [0-9.,]表明匹配一个数字,或者一个全角句号,或者一个逗号
  • [0-9a-fA-F]意味着匹配一个十六进制数
  • [a-zA-Z0-9-]意味着匹配一个字母、数字或者一个短横线
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
  • [^a]表示匹配任何不是“a”的字符
  • [^a-zA-Z0-9]表示匹配任何不是字母也不是数字的字符
  • [^abc]匹配一个为“^”或者a或者b或者c的字符
  • [^^]表示匹配任何不为“^”的字符

练习
在字典中,找到一个不满足“在e之前有i,但是没有c”的例子。答案
cie和[^c]ei都要可以找到很多这样的例子,比如ancient,science,viel,weigh
转义字符类
d这个正则表达式与[0-9]作用相同,都是匹配任何一个数字。(要匹配d,应该使用正则表达式d)
w与[0-9A-Za-z]相同,都表示匹配一个数字或字母字符
s意味着匹配一个空字符(空格,制表符,回车或者换行)
另外

  • D与[^0-9]相同,表示匹配一个非数字字符。
  • W与[^0-9A-Za-z]相同,表示匹配一个非数字同时不是字母的字符。
  • S表示匹配一个非空字符。
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]dddd-dd-dd
  • 正则表达式a{1}与a意思相同,都表示匹配字母a
  • a{3}表示匹配字符串“aaa”
  • a{0}表示匹配空字符串。从这个正则表达式本身来看,它毫无意义。如果你对任何文本执行这样的正则表达式,你可以定位到搜索的起始位置,即使文本为空。
  • a{2}表示匹配字符串“a{2}”
  • 在字符类中,大括号没有特殊含义。[{}]表示匹配一个左边的大括号,或者一个右边的大括号

练习
简化下面的正则表达式

z.......zdddd-dd-dd[aeiou][aeiou][aeiou][aeiou][aeiou][aeiou][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]

答案

z.{7}zd{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。
  • a{1,}表示匹配一个或一个以上的连续字符a。依然是匹配最长字符串。当找到第一个a之后,正则表达式会尝试匹配尽量多个的连续字母a。
  • .{0,}表示匹配任意内容。无论你输入的文本是什么,即使是一个空字符串,这个正则表达式都会成功匹配全文并返回结果。
 ".{0,}""[^"]{0,}"
  • ?*+ 表示匹配字符串”?*+”
  • [?*+]表示匹配一个问号,或者一个*号,或者一个加号

练习
简化下列的正则表达式:

".{0,}""[^"]{0,}"x?x?x?y*y*z+z+z+z+

答案

".*""[^"]*"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}?表示匹配dddd或者ddddd。也就是和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|waa|ab|ba|bb[abc]|[^abc][^ab]|[^bc][ab][ab][ab]?[ab]?

答案

[s-w][ab]{2}.[^b][ab]{2,4}
[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}
  • b表示匹配一个单词分隔符
  • bwwwb表示匹配一个三字母单词
  • aba表示匹配两个a中间有一个单词分隔符。这个正则表达式永远不会有匹配的字符,无论输入怎样的文本。

单词分隔符本身并不是字符。它们的宽度为0。下列正则表达式的作用不同

(bcat)b(bcatb)b(cat)bb(catb)

练习
在词典中找到最长的单词。答案
在尝试之后发现,b.{45,}b可以在字典中找到最长单词
换行符
一篇文本中可以有一行或多行,行与行之间由换行符分隔,比如:

  • Line一行文字
  • Line break换行符
  • Line一行文字
  • Line break换行符
  • Line break换行符
  • Line一行文字

注意,所有的文本都是以一行结束的,而不是以换行符结束。但是,任意一行都可能为空,包括最后一行。
行的起始位置,是在换行符和下一行首字符之间的空间。考虑到单词分隔符,文本的起始位置也可以当做是首行位置。
最后一行是最后一行的尾字符和换行符之间的空间。考虑到单词分隔符,文本的结束也可以认为是行的结束。
那么新的格式表示如下:

  • Start-of-line, line, end-of-line
  • Line break
  • Start-of-line, line, end-of-line
  • Line break
  • Line break
  • Start-of-line, line, end-of-line

基于上述概念:

^.*&表示匹配全文内容,因为行的开始符号也是一个字符,"."会匹配这个符号。找到单独的一行,可以使用

与字符分隔符一样,换行符也不是字符。它们宽度为0.如下所示的正则表达式作用不同:

(^cat)$(^cat$)^(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
  • 如果输入文本为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

  • 从正则表达式(dd)/(dd)/(dd)开始。注意,这其中有三个捕获组:月份,日期和两位的年份。
203-1-22005-03-04

在替换表达式中,你可以多次使用捕获组

  • 对于双元音,正则表达式为([aeiou]),替换表达式为ll
  • 在替换表达式中不能使用反斜杠。比如,你在计算机程序中希望使用字符串中使用部分文本。那么,你必须在每个双引号或者反斜杠之前加上反斜杠。
  • 你的正则表达式可以是(["])。捕获组1是双引号或者反斜杠
  • 你的替换表达式应该是l
1:2papacocob(.{6,})1bchiquichiqui(.{7,})1匹配countercountermeasurecountercountermeasures
"[^"]*"[]String re = "s";String re = "[ trn]";

在其他的编程语言中,正则表达式是由特殊标明的,比如使用/。下面是JavaScript的例子:

var regExp = /d/;var regExp = /[[]]/;var regExp = /s/;var regExp = /[ trn]/;var regExp = /https?:///;
PatternSyntaxException1234 5678 8765 4321 网站拒绝接收。因为它使用了正则表达式d{16}。正则表达式应该考虑到用户输入的空格和短横线。D*(dD*){16}
  • 名字中不含空格
  • 名字中没有连接符号
  • 名字中只会使用ASCII码字符
  • 名字中出现的字都在特殊字符集中
  • 名字至少要有M个字的长度
  • 名字不会超过N个字的长度
  • 人们只有一个名
  • 人们只有一个中间名
  • 人们只有一个姓(最后三条是从英语的人名考虑)


电子邮件地址
不要使用正则表达式验证邮箱地址的正确性。
首先,这样的验证很难是精确的。电子邮件地址是可以用正则表达式验证的,但是表达式会非常的长并且复杂。
短的正则表达式会导致错误。(你知道吗?电子邮箱地址中会有一些注释)
第二,即使一个电子邮件地址可以成功匹配正则表达式,也不代表这个邮箱实际存在。邮箱的唯一验证方法,是发送验证邮件。
注意
在严格的应用场景中,不要使用正则表达式来解析HTML或者XML。解析HTML或者XML:

  1. 使用简单的正则表达式不能完成
  2. 总体来说非常困难
  3. 已经有其他的方法解决

找到一个已经有的解析库来完成这个工作
这就是55分钟的全部内容
总结:

abcd1234.[abc][a-z]dws.d 表示w[0-9A-Za-z_]s [^abc]DWS
{4}{3,16}{1,}?*+?*+(Septem|Octo|Novem|Decem)berb^$Az123
.[]{}?*+|()^$[]-^

致谢
正则表达式非常常用而且非常有用。每个人在编辑文本或是编写程序时都必须了解怎样使用正则表达式。练习
选择正则表达式的某种实现,阅读相关文档。我保证,你会学到更多。