正则基础
# 历史
最初的正则表达式出现于理论计算机科学的自动控制理论和形式化语言理论中。在这些领域中有对计算(自动控制)的模型和对形式化语言描述与分类的研究。
1940年,沃伦·麦卡洛克与沃尔特·皮茨将神经系统中的神经元描述成小而简单的自动控制元。
1950年代,数学家斯蒂芬·科尔·克莱尼利用称之为“正则集合”的数学符号来描述此模型。肯·汤普逊将此符号系统引入编辑器QED,随后是Unix上的编辑器ed,并最终引入grep。自此以后,正则表达式被广泛地应用于各种Unix或类Unix系统的工具中。正则表达式的POSIX规范,分为基本型正则表达式(Basic Regular Expression,BRE)和扩展型正则表达式(Extended Regular Express,ERE)两大流派。在兼容POSIX的UNIX系统上,grep和egrep之类的工具都遵循POSIX规范,一些数据库系统中的正则表达式也匹配POSIX规范。grep、vi、sed都属于BRE,是历史最早的正则表达式,因此元字符必须转译之后才具有特殊含义。egrep、awk则属于ERE,元字符不用转译
Perl的正则表达式源自于Henry Spencer于1986年1月19日发布的regex,它已经演化成了PCRE(Perl兼容正则表达式,Perl Compatible Regular Expressions),一个由Philip Hazel开发的,为很多现代工具所使用的库。
# POSIX规范
POSIX的全称是Portable Operating System Interface for unix. 分为BRE(基本型正则表达式)和ERE(扩展型正则表达式)两大流派。
BRE流派
Linux下的vi、grep、sed工具属于BRE这一派,BRE中元字符(、)、{、}必须转义之后才具有特殊意义,比如a{1,2}才能匹配字符串a或aa。
BRE不支持+、?量词,多选结构和反向引用\1、\2。
GNU对BRE做了扩展,使之支持+、?、|,但使用时需转义。也支持\1、\2之类的反向引用。
ERE流派
Linux下的egrep、awk属于ERE流派。这一流派中使用元字符时不用转义,支持量词等。
现在的BRE和ERE的主要差异是元字符是否需转义。
正则表达式特性 | BREs | EREs |
---|---|---|
点号、^、$、[...]、[^...] | √ | √ |
“任意数目”量词 | * | * |
+ 和 ? 量词 | +、? | |
区间量词 | \{min, max\} | {min, max} |
分组 | \(...\) | (...) |
量词可否作用于括号 | √ | √ |
反向引用 | \1到\9 | |
多选结构 | √ |
# POSIX字符组
POSIX字符组 | 说明 | ASCII环境 | Unicode环境 |
---|---|---|---|
[:alnum:] | 字母字符和数字字符 | [a-zA-Z0-9] | [\p{L&}\p{Nd}] |
[:alpha:] | 字母 | [a-zA-Z] | \p{L&} |
[:ascii:] | ASCII字符 | [\x00-\x7F] | \p{InBasicLatin} |
[:blank:] | 空格字符和制表符 | [ \t] | [\p{Zs}\t] |
[:cntrl:] | 控制字符 | [\x00-\x1F\x7F] | \p{Cc} |
[:digit:] | 数字字符 | [0-9] | \p{Nd} |
[:graph:] | 空白字符之外的字符 | [\x21-\x7E] | [^\p{Z}\p{C}] |
[:lower:] | 小写字母字符 | [a-z] | \p{Ll} |
[:print:] | 类似[:graph:] ,但包括空白字符 | [\x20-\x7E] | \P{C} |
[:punct:] | 标点符号 | [][!"#$%&'()*+,./:;<=>?@\^_{|}~-] | [\p{P}\p{S}] |
[:space:] | 空白字符 | [\t\r\n\v\f] | [\p{Z}\t\r\n\v\f] |
[:upper:] | 大写字母字符 | [A-Z] | \p{Lu} |
[:word:] | 字母字符 | [A-Za-z0-9_] | [\p{L}\p{N}\p{Pc}] |
[:xdigit:] | 十六进制字符 | [A-Fa-f0-9] | [A-Fa-f0-9] |
# Unicode处理
在.NET、Java、JavaScript、Python的正则表达式中,可以用\uXXXX
表示一个Unicode字符,其中XXXX
为四位16进制数字。
Unicode字符的三种性质:
- Unicode Property:字符属于标点、空格、字母等等。每个Unicode字符只能属于唯一Unicode Property。.NET、Java、PHP和Ruby等语言支持。具体分类为:
- 字符
\p{L}
\p{Ll}
或\p{Lowercase_Letter}
:小写字符(必须有大写的形式)。\p{Lu}
或\p{Uppercase_Letter}
:大写字符(必须有小写的形式)。\p{Lt}
或\p{Titlecase_Letter}
:全词首字母大写的字符。\p{L&}
或\p{Cased_Letter}
:存在大小写形式的字符(Ll, Lu, Lt的组合)。\p{Lm}
或\p{Modifier_Letter}
:音标修饰字符。\p{Lo}
或\p{Other_Letter}
:不具有大小写的字符或字形。
- 附加符号
\p{M}
\p{Mn}
或\p{Non_Spacing_Mark}
:与其他字符结合,不额外占用空间的字符,例如日耳曼语元音变音。\p{Mc}
或\p{Spacing_Combining_Mark}
:与其他字符结合,额外占用空间的字符,例如马拉雅拉姆文#元音字母及附标)。\p{Me}
或\p{Enclosing_Mark}
:包含其他字符的字符,例如圆圈、方块。
- 分隔符
\p{Z}
\p{Zs}
或\p{Space_Separator}
:不可见的空格,但占据空间。\p{Zl}
或\p{Line_Separator}
:分隔线字符U+2028。\p{Zp}
或\p{Paragraph_Separator}
:分段字符U+2029。
- 符号
\p{S}
\p{Sm}
或\p{Math_Symbol}
:数学符号。\p{Sc}
或\p{Currency_Symbol}
:通货符号。\p{Sk}
或\p{Modifier_Symbol}
:组合为其他字符的符号。\p{So}
或\p{Other_Symbol}
:其他符号。
- 数值字符
\p{N}
\p{Nd}
或\p{Decimal_Digit_Number}
:所有文本中的数字0至9字符,不含形意符号)。\p{Nl}
或\p{Letter_Number}
:看起来像字母的符号,包含罗马数字。\p{No}
或\p{Other_Number}
:上角标或下角标数字,或者其他不属于0至9的数字。不含形意符号。
- 标点符号
\p{P}
\p{Pd}
或\p{Dash_Punctuation}
:任何种类的连字号或连接号。\p{Ps}
或\p{Open_Punctuation}
:任何种类开括号。\p{Pe}
或\p{Close_Punctuation}
:任何种类闭括号。\p{Pi}
或\p{Initial_Punctuation}
:任何种类开引号。\p{Pf}
或\p{Final_Punctuation}
:任何种类闭引号。\p{Pc}
或\p{Connector_Punctuation}
:连接词的标点符号,如下划线。\p{Po}
或\p{Other_Punctuation}
:其他标点符号。
- 其它符号
\p{C}
(包括不可见控制字符与未用码位)\p{Cc}
或\p{Control}
:ASCII或Latin-1控制字符0x00-0x1F
与0x7F-0x9F
。\p{Cf}
或\p{Format}
:不可见的格式化指示字符。\p{Co}
或\p{Private_Use}
:私用码位。\p{Cs}
或\p{Surrogate}
:UTF-16编码的代理对的一半。\p{Cn}
或\p{Unassigned}
:未被使用的码位。
- 字符
- Unicode Block:按照编码区间划分Unicode字符,每个Unicode Block中的字符编码属于一个编码区间。例如Java语言
\p{ InCJK_Compatibility_Ideographs }
,.NET语言\p{IsCJK_Compatibility_Ideographs}
。 - Unicode Script:按照字符所属的书写系统来划分Unicode字符。PHP和Ruby(版本不低于1.9)支持Unicode Script。例如
\p{Han}
表示汉字(中文字符)。
这三种Unicode性质对应的字符组补集是将开头的\p
改为\P
,其它不变。
# 优先级
优先权 | 符号 |
---|---|
最高 | \ |
高 | () 、(?:) 、(?=) 、[] |
中 | * 、+ 、? 、{n} 、{n,} 、{n,m} |
低 | ^ 、$ 、中介字符 |
次最低 | 串接,即相邻字符连接在一起 |
最低 | | |
# 语系对正规表示法的影响
既然正规表示法是处理字符串的一种表示方式,那么对字符排序有影响的语系数据就会对正规表示法的结果有影响!
为什么语系的数据会影响到正规表示法的输出结果呢?我们在第零章计算器概论的文字编码系统里面谈到,文件其实记录的仅有 0 与 1,我们看到的字符文字与数字都是透过编码表转换来的。由于不同语系的编码数据并不相同,所以就会造成数据撷取结果的差异了。 举例来说,在英文大小写的编码顺序中,zh_TW.big5 及 C 这两种语系的输出结果分别如下:
- LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z
- LANG=zh_TW 时:0 1 2 3 4 ... a A b B c C d D ... z Z
上面的顺序是编码的顺序,我们可以很清楚的发现这两种语系明显就是不一样!如果你想要撷取大写字符而使用 [A-Z] 时, 会发现 LANG=C 确实可以仅捉到大写字符 (因为是连续的) ,但是如果LANG=zh_TW.big5 时,就会发现到, 连同小写的 b-z 也会被撷取出来!因为就编码的顺序来看,big5 语系可以撷取到『 A b B c C ... z Z 』这一堆字符哩! 所以,使用正规表示法时,需要特别留意当时环境的语系为何, 否则可能会发现与别人不相同的撷取结果喔!
由于一般我们在练习正规表示法时,使用的是兼容于 POSIX 的标准,因此就使用『 C 』这个语系! 因此,底下的很多练习都是使用『 LANG=C 』这个语系数据来进行的喔! 另外,为了要避免这样编码所造成的英文与数字的撷取问题,因此有些特殊的符号我们得要了解一下的! 这些符号主要有底下这些意义:
# 正则表达式基本语法
字符 | 描述 |
---|---|
\ | 将下一个字符标记为一个特殊字符(File Format Escape,清单见本表)、或一个原义字符(Identity Escape,有^$()*+?.[\{| 共计12个)、或一个向后引用(backreferences)、或一个八进制转义符。例如,“n ”匹配字符“n ”。“\n ”匹配一个换行符。序列“\\ ”匹配“\ ”而“\( ”则匹配“( ”。 |
^ | 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n ”或“\r ”之后的位置。 |
$ | 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n ”或“\r ”之前的位置。 |
* | 匹配前面的子表达式零次或多次。例如,zo* 能匹配“z ”、“zo ”以及“zoo ”。* 等价于{0,}。 |
+ | 匹配前面的子表达式一次或多次。例如,“zo+ ”能匹配“zo ”以及“zoo ”,但不能匹配“z ”。+等价于{1,}。 |
? | 匹配前面的子表达式零次或一次。例如,“do(es)? ”可以匹配“does ”中的“do ”和“does ”。?等价于{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? ”。请注意在逗号和两个数之间不能有空格。 |
? | 非贪心量化(Non-greedy quantifiers):当该字符紧跟在任何一个其他重复修饰符(*,+,?,{n},{n,},{n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo ”,“o+? ”将匹配单个“o ”,而“o+ ”将匹配所有“o ”。 |
. | 匹配除“\r ”“\n ”之外的任何单个字符。要匹配包括“\r ”“\n ”在内的任何字符,请使用像“(.|\r|\n) ”的模式。 |
(pattern) | 匹配pattern并获取这一匹配的子字符串。该子字符串用于向后引用。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\( ”或“\) ”。可带数量后缀。 |
(?:pattern) | 匹配pattern但不获取匹配的子字符串(shy groups),也就是说这是一个非获取匹配,不存储匹配的子字符串用于向后引用。这在使用或字符“(|) ”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies) ”就是一个比“industry|industries ”更简略的表达式。 |
(?=pattern) | 正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000) ”能匹配“Windows2000 ”中的“Windows ”,但不能匹配“Windows3.1 ”中的“Windows ”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。 |
(?!pattern) | 正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000) ”能匹配“Windows3.1 ”中的“Windows ”,但不能匹配“Windows2000 ”中的“Windows ”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始 |
(?<=pattern) | 反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows ”能匹配“2000Windows ”中的“Windows ”,但不能匹配“3.1Windows ”中的“Windows ”。 |
(?<!pattern) | 反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows ”能匹配“3.1Windows ”中的“Windows ”,但不能匹配“2000Windows ”中的“Windows ”。 |
x|y | 没有包围在()里,其范围是整个正则表达式。例如,“z|food ”能匹配“z ”或“food ”。“(?:z|f)ood ”则匹配“zood ”或“food ”。 |
[xyz] | 字符集合(character class)。匹配所包含的任意一个字符。例如,“[abc] ”可以匹配“plain ”中的“a ”。特殊字符仅有反斜线\保持特殊含义,用于转义字符。其它特殊字符如星号、加号、各种括号等均作为普通字符。脱字符^如果出现在首位则表示负值字符集合;如果出现在字符串中间就仅作为普通字符。连字符 - 如果出现在字符串中间表示字符范围描述;如果如果出现在首位(或末尾)则仅作为普通字符。右方括号应转义出现,也可以作为首位字符出现。 |
[^xyz] | 排除型字符集合(negated character classes)。匹配未列出的任意字符。例如,“[^abc] ”可以匹配“plain ”中的“plin ”。 |
[a-z] | 字符范围。匹配指定范围内的任意字符。例如,“[a-z] ”可以匹配“a ”到“z ”范围内的任意小写字母字符。 |
[^a-z] | 排除型的字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z] ”可以匹配任何不在“a ”到“z ”范围内的任意字符。 |
[:name:] | 增加命名字符类(named character class)。只能用于方括号表达式。 |
[=elt=] | 增加当前locale下排序(collate)等价于字符“elt”的元素。例如,[=a=] 可能会增加ä、á、à、ă、ắ、ằ、ẵ、ẳ、â、ấ、ầ、ẫ、ẩ、ǎ、å、ǻ、ä、ǟ、ã、ȧ、ǡ、ą、ā、ả、ȁ、ȃ、ạ、ặ、ậ、ḁ、ⱥ、ᶏ、ɐ、ɑ 。只能用于方括号表达式。 |
[.elt.] | 增加排序元素 (opens new window)(collation element)elt到表达式中。这是因为某些排序元素由多个字符组成。例如,29个字母表的西班牙语, "CH"作为单个字母排在字母C之后,因此会产生如此排序“cinco, credo, chispa”。只能用于方括号表达式。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b ”可以匹配“never ”中的“er ”,但不能匹配“verb ”中的“er ”。 |
\B | 匹配非单词边界。“er\B ”能匹配“verb ”中的“er ”,但不能匹配“never ”中的“er ”。 |
\cx | 匹配由x指明的控制字符。x的值必须为A-Z 或a-z 之一。否则,将c视为一个原义的“c ”字符。控制字符的值等于x的值最低5比特(即对3210进制的余数)。例如,\cM 匹配一个Control-M或回车符。\ca 等效于\u0001 , \cb 等效于\u0002 , 等等… |
\d | 匹配一个数字字符。等价于[0-9] 。注意Unicode正则表达式会匹配全角数字字符。 |
\D | 匹配一个非数字字符。等价于[^0-9] 。 |
\f | 匹配一个换页符。等价于\x0c 和\cL 。 |
\n | 匹配一个换行符。等价于\x0a 和\cJ 。 |
\r | 匹配一个回车符。等价于\x0d 和\cM 。 |
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[\f\n\r\t\v] 。注意Unicode正则表达式会匹配全角空格符。 |
\S | 匹配任何非空白字符。等价于[^ \f\n\r\t\v] 。 |
\t | 匹配一个制表符。等价于\x09 和\cI 。 |
\v | 匹配一个垂直制表符。等价于\x0b 和\cK 。 |
\w | 匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_] ”。注意Unicode正则表达式会匹配中文字符。 |
\W | 匹配任何非单词字符。等价于“[^A-Za-z0-9_] ”。 |
\xnn | 十六进制转义字符序列。匹配两个十六进制数字nn表示的字符。例如,“\x41 ”匹配“A ”。“\x041 ”则等价于“\x04&1 ”。正则表达式中可以使用ASCII编码。. |
\num | 向后引用(back-reference)一个子字符串(substring),该子字符串与正则表达式的第num个用括号围起来的捕捉群(capture group)子表达式(subexpression)匹配。其中num是从1开始的十进制正整数,其上限可能是9(命名字符类BRE与grep最多只能向后引用到9;Visual C++的regex库最多只能向后引用到31;ECMAScript不限向后引用的上限)。例如:“(.)\1 ”匹配两个连续的相同字符。 |
\n | 标识一个八进制转义值或一个向后引用。如果\n 之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。 |
\nm | 3位八进制数字,标识一个八进制转义值或一个向后引用。如果\nm 之前至少有nm个获得子表达式,则nm为向后引用。如果\nm 之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若n和m均为八进制数字(0-7),则\nm 将匹配八进制转义值nm。 |
\nml | 如果n为八进制数字(0-3),且m和l均为八进制数字(0-7),则匹配八进制转义值nml。 |
\un | Unicode转义字符序列。其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9 匹配著作权符号(©)。 |
# 匹配中文
参考链接:https://www.cnblogs.com/animalize/p/5432864.html
以下是比较全面的汉字Unicode分布,参考Unicode 10.0标准(2017年6月发布):
区块 | 范围 | 实际汉字个数/备注 | 正则式 |
---|---|---|---|
CJK统一汉字 | 4E00-62FF, 6300-77FF,7800-8CFF, 8D00-9FFF | 20,971常见 | [\u4E00-\u9FFF] 或[一-鿆] |
CJK统一汉字扩展A区 | 3400-4DBF | 6,582罕见 | [\u3400-\u4DBF] |
CJK统一汉字扩展B区 | 20000-215FF, 21600-230FF,23100-245FF, 24600-260FF,26100-275FF, 27600-290FF,29100-2A6DF | 42,711罕见,历史 | [\U00020000-\U0002A6DF] |
CJK统一汉字扩展C区 | 2A700-2B73F | 4,149罕见,历史 | [\U0002A700-\U0002B73F] |
CJK统一汉字扩展D区 | 2B740–2B81F | 222不常见,仍在使用 | [\U0002B740-\U0002B81F] |
CJK统一汉字扩展E区 | 2B820–2CEAF | 5,762罕见,历史 | [\U0002B820-\U0002CEAF] |
CJK统一汉字扩展F区 | 2CEB0-2EBEF | 7,473罕见,历史 | [\U0002CEB0-\U0002EBEF] |
CJK兼容汉字 | F900–FAFF | 472重复、可统一变体、公司定义 | [\uF900-\uFAFF] |
CJK兼容汉字增补 | 2F800-2FA1F | 542可统一变体 | [\U0002F800-\U0002FA1F] |
★如果想表示最普遍的汉字
用:[\u4E00-\u9FFF] 或 [一-鿆] 共有20950个汉字,包括了常用简体字和繁体字,镕等字。 基本就是GBK的所有(21003个)汉字。也包括了BIG5的所有(13053个)繁体汉字。 一般情况下这个就够用了。
说明:仅仅未包括出现在GBK里的CJK兼容汉字的21个汉字:郎凉秊裏隣兀嗀﨎﨏﨑﨓﨔礼﨟蘒﨡﨣﨤﨧﨨﨩 CJK兼容汉字用于转码处理,日常中是用不到的,所以不包括也没什么问题。 注意此凉非彼凉,兀也不是常用的那个,虽然用眼睛看是一样的,参见 http://www.zhihu.com/question/20697984
★如果想表示BMP之内的汉字
也就是Unicode值<=0xFFFF之内的所有汉字,用:[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF],这个包含但不限于GBK定义的汉字,共有28025个汉字。
说明:和上面相比,主要是多了CJK统一汉字扩展A区,这是1999年收录到Unicode 3.0标准里的6,582个汉字。 CJK统一汉字扩展A区,包括了东亚各地区(陆港台日韩新越)的汉字,有很多康熙字典的繁体字。
★ 如果想尽可能表示所有的汉字
用:[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\U00020000-U0002EBEF]这个包含上表的所有88342个汉字
说明: 1, 以上正则表达式不会匹配(英文、汉字的)标点符号,不会匹配韩国拼音字、日本假名。 2, 会匹配一些日本、韩国独有的汉字。 3, 包含了一些没有汉字的空位置,这通常不碍事。 4, \u及\U的正则语法在Python 3.5上测试通过。 有些正则表达式引擎不认\uFFFF和\UFFFFFFFF这样的语法,可以换成\x{FFFF}试一下;有些不支持BMP之外的范围,这就没办法处理CJK统一汉字扩展B~E区了,如notepad++。