编程工具(一)正则表达式
参考:《正则表达式必知必会》
参考:正则表达式必知必会
一、正则表达式
正则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开的。从诞生之初的深奥数学概念,到现在正则表达式已经成为了计算机编程技术的基础技术,主流操作系统(*nix[Linux, Unix等]、Windows、HP、BeOS等)、主流的开发语言(delphi、Scala、PHP、C#、Java、C++、Objective-c、Swift、VB、Javascript、Ruby以及Python等)和数以亿万计的各种应用软件中,都可以看到正则表达式优美的舞姿。
正则表达式的目的:
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
1. 给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”);
2. 可以通过正则表达式,从字符串中获取我们想要的特定部分。
二、正则表达式的规则
2.1 元字符
正则表达式可以理解成一门描述语言,而元字符可以理解成不可再分的关键字(key word)。元字符是一些在正则表达式中有着特殊含义的字符,英文句号(.)就是一个元字符,它可以用来匹配任何一个单个字符。
下表包含了元字符的完整列表以及它们在正则表达式上下文中的行为:
元字符
|
描述
|
\
|
将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如,“\\n”匹配\n。“\n”匹配换行符。序列“\\”匹配“\”而“\(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。
|
^
|
匹配输入字行首。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。
|
$
|
匹配输入行尾。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。
|
*
|
匹配前面的子表达式任意次。例如,zo*能匹配“z”,也能匹配“zo”以及“zoo”。*等价于o{0,}
|
+
|
匹配前面的子表达式一次或多次(大于等于1次)。例如,“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。
|
?
|
匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“do”或“does”中的“do”。?等价于{0,1}。
|
{n}
|
n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。
|
{n,}
|
n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。
|
{n,m}
|
m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o为一组,后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。
|
?
|
当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o+”将尽可能多的匹配“o”,得到结果[“oooo”],而“o+?”将尽可能少的匹配“o”,得到结果 ['o', 'o', 'o', 'o']
|
.点
|
匹配除“\r\n”之外的任何单个字符。要匹配包括“\r\n”在内的任何字符,请使用像“[\s\S]”的模式。
|
(pattern)
|
匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“
”或“”或“。
|
* | 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 |
+ | 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。 |
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 |
[ | 标记一个中括号表达式的开始。要匹配 [,请使用 \[。 |
? | 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?。 |
\ | 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如, 'n' 匹配字符 'n'。'\n' 匹配换行符。序列 '\\' 匹配 "\",而 '\(' 则匹配 "("。 |
^ | 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 |
{ | 标记限定符表达式的开始。要匹配 {,请使用 \{。 |
| | 指明两项之间的一个选择。要匹配 |,请使用 \|。 |
2.2 字符
普通字符
普通字符包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。
可打印字符的匹配:
- \d 匹配任何 0 到 9 之间的单个数字,相当于 [0-9]
- \s 匹配任何空白字符,相当于 [ \f\n\r\t\v]
- \w 匹配大小写英文字符及数字 0 到 9 之间的任意一个及下划线,相当于 [a-zA-Z0-9_]
- [\u4e00-\u9fa5] 匹配任意单个汉字(这里用的是 Unicode 编码表示汉字的 )
不可打印字符的匹配:
- \cx 匹配由x指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 'c' 字符。
- \f 匹配一个换页符。等价于 \x0c 和 \cL。
- \n 匹配一个换行符。等价于 \x0a 和 \cJ。
- \r 匹配一个回车符。等价于 \x0d 和 \cM。
- \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
- \t 匹配一个制表符。等价于 \x09 和 \cI。
- \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。
常用反义元字符
- \W 匹配任意不是字母,数字,下划线,汉字的字符
- \S 匹配任意不是空白符的字符
- \D 匹配任意非数字的字符
- \B 匹配不是单词开头或结束的位置
- [^x] 匹配除了x以外的任意字符
- [^aeiou] 匹配除了aeiou这几个字母以外的任意字符
2.3 限定符和贪婪
限定符用来指定正则表达式的一个给定组件必须要出现多少次才能满足匹配。有 * 或 + 或 ? 或 {n} 或 {n,} 或 {n,m} 共6种。
字符 | 描述 |
---|---|
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,'zo+' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。+ 等价于 {1,}。 |
? | 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。 |
{n} | n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。 |
{n,} | n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o+'。'o{0,}' 则等价于 'o*'。 |
{n,m} | m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。 |
由于章节编号在大的输入文档中会很可能超过九,所以需要一种方式来处理两位或三位章节编号。限定符可以容忍更多的位数可能。下面的正则表达式匹配编号为任何位数的章节标题:
/Chapter [1-9][0-9]*/
限定符出现在范围表达式之后。因此,它应用于整个范围表达式,在本例中,只指定从 0 到 9 的数字(包括 0 和 9)。这里不使用 + 限定符,因为在第二个位置或后面的位置不一定需要有一个数字。也不使用 ? 字符,因为使用 ? 会将章节编号限制到
只有两位数。您需要至少匹配 Chapter 和空格字符后面的一个数字。
只匹配两位数章节的写法如下:
上面的表达式的缺点是,大于 99 的章节编号仍只匹配开头两位数字。另一个缺点是 Chapter 0 也将匹配。只匹配两位数字的更好的表达式如下:
2.4 定位符
定位符使您能够将正则表达式固定到行首或行尾。它们还使您能够创建这样的正则表达式,这些正则表达式出现在一个单词内、在一个单词的开头或者一个单词的结尾。
定位符用来描述字符串或单词的边界,^ 和 $ 分别指字符串的开始与结束,\b 描述单词的前或后边界,\B 表示非单词边界。
正则表达式的定位符有:
字符 | 描述 |
---|---|
^ | 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。 |
$ | 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。 |
\b | 匹配一个字边界,即字与空格间的位置。 |
\B | 非字边界匹配。 |
若要匹配一行文本开始处的文本,请在正则表达式的开始使用 ^ 字符。不要将 ^ 的这种用法与中括号表达式内的用法混淆。
若要匹配一行文本的结束处的文本,请在正则表达式的结束处使用 $ 字符。
2.5 选择
用圆括号将所有选择项括起来,相邻的选择项之间用|分隔。但用圆括号会有一个副作用,使相关的匹配会被缓存,此时可用?:放在第一个选项前来消除这种副作用。
其中 ?: 是非捕获元之一,还有两个非捕获元是 ?= 和 ?!,这两个还有更多的含义,前者为正向预查,在任何开始匹配圆括号内的正则表达式模式的位置来匹配搜索字符串,后者为负向预查,在任何开始不匹配该正则表达式模式的位置来匹配搜索字符串。
- 用(?<groupname>exp)进行分组命名。默认地,每个括号所划分的组都用数字索引标记,如果括号之间嵌套过多容易造成混乱,所以用自定义分组命名比较方便明了。
- 用(?:exp)匹配文本但不为此分组编号。因为给分组编号或命名的目的是便于我们提取匹配出来的文本,但很多时候加括号并不是为了要局部把文本提取出来。
\b(?<protocol>https?|ftp):\/\/(?<domain>[-a-zA-Z0-9.]+)(?<file>\/[-a-zA-Z0-9+&@#\%=~_|!:,.;]*)?(?<parameters>\?[a-zA-Z0-9+&@#\%=~_|!:,.;]*)?
使用其对url进行解析可以捕获到文本并按照自定义分组名缓存。
2.6 反向引用
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储99 个捕获的子表达式。每个缓冲区都可以使用 \n 访问,其中 n 为一个标识特定缓冲区的一位或两位十进制数。
可以使用非捕获元字符 ?:、?= 或 ?! 来重写捕获,忽略对相关匹配的保存。
反向引用的最简单的、最有用的应用之一,是提供查找文本中两个相同的相邻单词的匹配项的能力。以下面的句子为例:
Is is the cost of of gasoline going up up?
如果能设计一种方法定位该句子,而不必查找每个单词的重复出现,那该有多好。下面的正则表达式使用单个子表达式来实现这一点:
捕获的表达式,正如 [a-z]+ 指定的,包括一个或多个字母。正则表达式的第二部分是对以前捕获的子匹配项的引用,即,单词的第二个匹配项正好由括号表达式匹配。\1 指定第一个子匹配项。字边界元字符确保只检测整个单词。否则,诸如 "is issued" 或 "this is" 之类的词组将不能正确地被此表达式识别。
正则表达式后面的全局标记 g 指定将该表达式应用到输入字符串中能够查找到的尽可能多的匹配。表达式的结尾处的不区分大小写 i 标记指定不区分大小写。
2.7 零宽断言
断言就是判断真假,只有断言为真时,正则表达式才会往下匹配。通常来说,用一般的正则式匹配出来的文本都是要占一定宽度的。比如,现在有一测试文本”Script JavaScript ShellScript Javac Java”。要匹配”JavaScript”中的”Script”,若用\bJava这个断言的话,”Java” 就会在匹配出来的文本中占4个宽度。
怎么让”Java” 不出现在匹配出的文本当中 ,又能 表达作为匹配文本前缀的断言作用呢 ?零宽断言就是为此而生。
正向零宽断言 :
- (?<=\bJava)Script 匹配出以”Java”为前缀的”Script”。即”Script Java**Script** ShellScript Javac Java”。
- Java(?=Script\b)匹配出以”Script” 为后缀的”Java“。即”Script **Java**Script ShellScript Javac Java”。
注意,它们上面两种断言语法的细微差异,当原来的要占位断言表达式在前面,用(?<=exp)包住表达式;当原来的要占位的断言表达式在后面,用(?=exp)包住表达式。下面的负向零宽断言也差不多。
负向零宽断言:
- Java(?!c) 匹配出后面不跟”c”的”Java”。即”Script Java**Script ShellScript Javac **Java“。
- (?<!a)Script匹配出前一个字符不是”a”的”Script”。 即”Script JavaScript Shell**Script** Javac Java”
2.8 运算符优先级
正则表达式从左到右进行计算,并遵循优先级顺序,这与算术表达式非常类似。相同优先级的从左到右进行运算,不同优先级的运算先高后低。下表从最高到最低说明了各种正则表达式运算符的优先级顺序:
运算符 | 描述 |
---|---|
\ | 转义符 |
(), (?:), (?=), [] | 圆括号和方括号 |
*, +, ?, {n}, {n,}, {n,m} | 限定符 |
^, $, \任何元字符、任何字符 | 定位点和序列(即:位置和顺序) |
| | 替换,"或"操作 字符具有高于替换运算符的优先级,使得"m|food"匹配"m"或"food"。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m|f)ood"。 |
2.9 匹配规则
正则表达式具有基本模式匹配和字符簇两种匹配方式。
基本模式匹配
基本模式匹配是平铺直叙式的匹配,使用单句正则语句进行匹配:
字符簇
字符簇代表一个表示所有元音字符的字符簇,就把所有的元音字符放在一个方括号里:
[AaEeIiOoUu]
这个模式与任何元音字符匹配,但只能表示一个字符。用连字号可以表示一个字符的范围,如:
[a-z] //匹配所有的小写字母 [A-Z] //匹配所有的大写字母 [a-zA-Z] //匹配所有的字母 [0-9] //匹配所有的数字 [0-9\.\-] //匹配所有的数字,句号和减号 [ \f\r\t\n] //匹配所有的白字符
同样的,这些也只表示一个字符,这是一个非常重要的。如果要匹配一个由一个小写字母和一位数字组成的字符串,比如"z2"、"t6"或"g7",但不是"ab2"、"r2d3" 或"b52"的话,用这个模式:
^[a-z][0-9]$
尽管[a-z]代表26个字母的范围,但在这里它只能与第一个字符是小写字母的字符串匹配。
前面曾经提到^表示字符串的开头,但它还有另外一个含义。当在一组方括号里使用^是,它表示"非"或"排除"的意思,常常用来剔除某个字符。还用前面的例子,我们要求第一个字符不能是数字:
^[^0-9][0-9]$
这个模式与"&5"、"g7"及"-2"是匹配的,但与"12"、"66"是不匹配的。下面是几个排除特定字符的例子:
[^a-z] //除了小写字母以外的所有字符 [^\\\/\^] //除了(\)(/)(^)之外的所有字符 [^\"\'] //除了双引号(")和单引号(')之外的所有字符
三、工具网站
RegExr:https://regexr.com/