正则表达式(Regular Expression)¶
精通正则表达式¶
正则表达式可以说是一门艺术,经验的积累才能达到更好的境界!
目前主要存在三种正则表达式的版本:Perl (PCRE), POSIX Basic Regular Expressions(BRE), POSIX Extended Regular Expressions(ERE).
第一章:正则表达式入门¶
^脱字符代表一行的开始,$美元符号代表一行的结尾。这里注意Singleline和Multiline模式的区别。
在字符组内部,字符组元字符 ‘-’(连字符)表示一个范围。只有在字符组内部,连字符才是元字符–否则它就只能匹配普通的连字符号。但是如果‘-’出现在字符组的开始,它表示的也是普通的字符。同样的道理,问号和点号通常被当作元字符处理,但在字符组中则代表普通的字符。(这点其实可以被利用)
下表是元字符及其在正则表达式上下文中的行为的一个完整列表:(自己来不断完善…)
字符 含意 \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个后向引用、或一个八进制转义符。 例如,’n’ 匹配字符 “n”。’\n’ 匹配一个换行符。序列 ‘' 匹配 “” 而 “\(” 则匹配 “(“。 ^ 匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。 $ 匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。 * 匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。 * 等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,’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{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。 ? 当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。 非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。 例如,对于字符串 “oooo”,’o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。 . 匹配除 “\n” 之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用象 ‘[.\n]’ 的模式。 (pattern) 匹配pattern 并获取这一匹配。 所获取的匹配可以从产生的 Matches 集合得到,要匹配圆括号字符,请使用 ‘\(‘ 或 ‘\)’。 (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。 这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。 例如, ‘industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。 (?=pattern) 肯定 顺序环视 :子表达式能够匹配右侧的文本正向预查,在任何匹配 pattern 的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如, ‘Windows (?=95|98|NT|2000)’ 能匹配 “Windows 2000” 中的 “Windows” , 但不能匹配 “Windows 3.1” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索, 而不是从包含预查的字符之后开始。(?!pattern) 否定顺序环视:子表达式不能匹配右侧的文本负向预查,在任何不匹配的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如’Windows (?!95|98|NT|2000)’ 能匹配 “Windows 3.1” 中的 “Windows”, 但不能匹配 “Windows 2000” 中的 “Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索, 而不是从包含预查的字符之后开始(?<=pattern))))) 肯定逆序环视:子表达式能够匹配左侧的文本 (?<!pattern))))) 否定逆序环视:子表达式不能匹配左侧的文本 x|y 匹配 x 或 y。例如,’z|food’ 能匹配 “z” 或 “food”。’(z|f)ood’ 则匹配 “zood” 或 “food”。 [xyz] 字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。 [^xyz] 负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’。 [a-z] 字符范围。匹配指定范围内的任意字符。例如,’[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。 [^a-z] 负值字符范围。匹配任何不在指定范围内的任意字符。 例如,’[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。 \b 匹配一个单词边界,也就是指单词和空格间的位置。 例如, ‘er\b’ 可以匹配”never” 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 \B 匹配非单词边界。’er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 \cx 匹配由x指明的控制字符。 例如, \cM 匹配一个 Control-M 或回车符。 x 的值必须为 A-Z 或 a-z 之一。否则将c视为一个原义的’c’字符。 \d 匹配一个数字字符。等价于 [0-9]。 \D 匹配一个非数字字符。等价于 [^0-9]。 \f 匹配一个换页符。等价于 \x0c 和 \cL。 \n 匹配一个换行符。等价于 \x0a 和 \cJ。 \r 匹配一个回车符。等价于 \x0d 和 \cM。 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。 \t 匹配一个制表符。等价于 \x09 和 \cI。 \v 匹配一个垂直制表符。等价于 \x0b 和 \cK。 \w 匹配包括下划线的任何单词字符。等价于’[A-Za-z0-9_]’。 \W 匹配任何非单词字符。等价于 ‘[^A-Za-z0-9_]’。 \xn 匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。 例如, ‘\x41’ 匹配 “A”。’\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。 \num 匹配 nuuuuum,其中 num 是一个正整数。对所获取的匹配的 反向引用 。例如’(.)’ 匹配两个连续的相同字符。 \n 标识一个八进制转义值或一个 反向引用 。 如果 \n 之前至少 n 个获取的子表达式,则 n 为后向引用。 否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。 \nm 标识一个八进制转义值或一个 反向引用 。 如果 \nm 之前至少有is preceded by at least nm 个获取得子表达式,则 nm 为后向引用。 如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的后向引用。 如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。 \nml 如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。 \un 匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如,\u00A9 匹配版权符号 (?)。 \G 匹配的起始位置、或者上一次匹配的结束位置 (?>pattern) 固化分组 :固化分组中的匹配一旦匹配,就是固定下来,不会保存回溯状态。 固化分组的实质就是抛弃备选状态! (?(if)then|else) 条件判断 :(?(expression)yes|no) 可以视为 (?(?=expression)yes|no)。注意:::正则表达式中作决策最重要的一点就是: 决策的模式expression的长度为0,代表了yes或no的正则表达式将从与expression相同的位置开始匹配.字符组的特殊性在于: 关于元字符的规定是完全独立于正则表达式语言”主体” 的.
- 多选结构和字符组是截然不同的,它们的功能完全不同,只是在有限的情况下,它们的表现相同。很明显,它们的作用域是不相同的,字符组只限定于一个字符,而多选结构则是一个完整的子表达式。
第二章:入门示例拓展¶
- 捕获型括号的编号顺序是以开括号出现的顺序来决定的。
- 环视最重要的特性就是不占用匹配字符,相当于只是站在那个地方看一看,但是并不走(传动)。
- 环视结构不匹配任何字符,只匹配文本中的特定位置,这一点与单词分界符\b、锚点^和$类似。
第三章:正则表达式的特性和流派概览¶
- 区分Unicode中的”代码点”和”字符”的概念,一个字符可能由多个代码点来构例如:à是由两个代码点构成(a)和钝重音的结合。许多程序似乎把”字符”和”代码点”视为等价,也就是说,点号可以匹配单个代码点,无论是基本字符还是组合字符。所以,à能够由[^..$]来匹配,面不是[^.$]。在支持Unicode的编辑器中输入正则表达式时,一定要记住组合字符的概念。比如[à]会被当作[a‘]。同样,如果两个代码点的字符如à后有一个量词,量词也只会作用于第二个代码点。
- “单行模式”所谓的意义就是”点号通配模式” :
在此模式下,点号可以匹配任何字符,包括换行符;锚点^不能匹配字符串内部的换行符,而只能匹配目标字符串的起始位置。
- “多行模式”所谓的意义就是”增强的行锚点模式” :
在此模式下,点号不能匹配换行符。锚点^能够匹配字符串中内嵌的文件行的开头位置。 支持这些模式的流派通常会提供\A、\Z来唯一的匹配整个字符串的开始和结束。
Unicode标准还定义了每个字符的性质,许多支持Unicode的程序能够通过\p{quality}来支持其中的一部分。
分类 等价表示法及描述 \p{L} 字母 \p{M} 不能单独出现,而必须与其它基本字符一起出现(重音符号、包围框、等等)的字符 \p{Z} 用于表示分隔,但本身不可见的字符(各种空白字符) \p{S} 各种图形符号和字母符号 \p{N} 任何数字字符 \p{P} 标点字符 \p{C} 匹配其它任何字符——很少用于正常字符 - .NET提供:简单的字符组减法:[[a-z]-[aeiou]]Java提供:完整的字符组集合运算:[[a-z]&&[^aeiou]]
第四章:表达式的匹配原理¶
优先选择最左端(最靠开头)的匹配结果。 标准的匹配量词(*、+、?和{m,n})是匹配优先的
正则匹配的基本原理:匹配先从需要查找的字符串的起始位置尝试匹配。 在这里,尝试匹配的意思是:在当前位置测试整个正则表达式(可能很复杂)能匹配的每样文本。 如果在当前位置测试了所有的可能之后不能找到匹配结果,就需要从字符串中的第二个字符之前的位置开始重新尝试。 在找到匹配结果以前必须在所有的位置重复此过程。 只有在尝试过所有的起始位置(直到字符串的最后一个字符)都不能找到匹配结果的情况下,才会报告”匹配失败”。
- NFA(表达式主导):是以表达式为中心,尝试找到正则表达式能匹配的文本的一部分。DFA(文本主导):以文本为中心,尝试能不能找出与文本相匹配的正则表达式的分支。
DFA不支持捕获型括号和回溯。
- NFA引擎最重要的性质是:回溯!
它会依次处理各个子表达式或组成元素,遇到需要在两个可能成功的可能中进行选择的时候,它会选择其一,同时记住另一个,以备稍后可能的需要。回溯使用的原则是LIFO(后进先出、极有可能使用的是栈来保存状态的)。
- 固化分组 :
具体来说 ,使用(?>…)的匹配与正常的匹配并无差别,但是如果 匹配进行到此结构之后(也就是,进行到固化分组的闭括号之后) ,那么此结构体中的所有备用状态都会被抛弃。(在固化分组的内部,该回溯的地方还是照样的回溯,只是在出固化分组后会抛弃备用状态)
环视结构与固化分组类似,在环视结束之后,所有的备用状态都会被抛弃,所以可以用环视来模拟固化分组。(?>regex)可以用(?=(regex))1来模拟。
匹配优先和忽略优先都不会影响需要检测路径的本身,而只会影响检测的顺序。如果不能匹配,无论是按照匹配优先还是忽略优先的顺序,最终每条路径都会被测试。
固化分组对提高效率很有用,因为可以利用它来抛弃不必要的备选分支。
多选结构的优先顺序:(所以,小心安排多选分支的顺序)
传统NFA:多选结构既不是匹配优先的,也不是忽略优先的,而是按照从左到右的顺序排列的。
DFA和POSIX NFA:它们总是匹配所有多选分支中能匹配最多文本的那个。
- POSIX标准规定:如果在字符串中的某个位置存在多个可能的匹配,应当返回的是最长的匹配。DFA会选择所有可能匹配中最长的匹配,称为”最左最长规则”。
NFA与DFA的比较:
- DFA的速度与正则表达式无关,而NFA中两者直接相关。
- DFA不需要太多的优化,因为它的速度很快。DFA在预编译时所做的优化效果,也要好于大多数NFA引擎复杂的优化措施。
- DFA(或者POSIX NFA)返回最左边的最长的匹配的文本。传统型NFA可能返回同样的结果,也可能是别的结果,取决于文本和正则表达式。
- NFA引擎能够提供许多DFA不支持的功能。如捕获括号、环视、忽略(占有)优先量词、固化分组等等。
POSIX NFA最大的特点是它最终必须尝试正则表达式的每一种可能,多选分支的顺序其实不重要。
第五章:正则表达式实用技巧¶
- 集中关注在特定时刻真正容许匹配的字符。
- .*经常被滥用,还是需要清楚的明白真正容许匹配和不容许匹配的字符。
- 标准的正则表达式无法匹配任意深度的嵌套结构,但是现在许多主流的语言都提供了解决方案,如.NET、Perl、PHP。
- 如果所有的正则部分都是可选结构,则往往是错误的,因为它会出现不期望的匹配。
- 可以用\G来屏蔽引擎自带的驱动装置。
第六章:打造高效的正则表达式¶
- 对于POSIX NFA,多个量词的嵌套会造成指数级的复杂度。而传统NFA在无法匹配时进行的尝试次数与POSIX NFA一样多,所以绝不要用多个量词的嵌套!
常见的优化措施:
- 加速某些操作:由于引擎可能经过特别的优化,所以\d往往会比[0-9]的效率更好。其它的类似。
- 避免冗余操作:如果以\A、^、\b、$等开头或结尾可以避免进行无效的传动,往往能提高效率。
正则表达式的应用原理:
- 正则表达式编译:检查语法的正确性,如果正确,就编译为内部的形式。
- 传动开始:将正则引擎”定位”到目标字符串的起始位置。
- 元素检测:引擎开始测试正则表达式和文本,依次测试正则表达式的各个元素。
- 寻找匹配结果:报告匹配成功或者继续寻找最长的匹配。
- 传动装置的驱动:如果没有找到匹配,传动装置会驱动引擎,从文本的下一个字符开始新的一轮尝试。
- 匹配彻底失败:如果从每一个字符开始都失败了,则报告失败。
许多情况下,占有优先量词、固化分组会大大的提高效率,而且它们不会改变匹配的结果。因为它会在退出分组时抛弃备用状态,从而大大的减少回溯的次数。而且聪明的引擎实现方式应该会自己做到这一点。
对于不太长的确定大小的量词,分开写也许更好。 ====比={4}快上100倍。同理:XX*代替X+、====={0,2}代替={5,7}。
不要滥用括号、不要滥用字符组,使用非捕获型括号。
Python 中的正则表达式 [1]¶
Python中的正则表达式由re模块提供(原始的regex模块已被弃用),它会使用由C语言编写的正则引擎,所以速度很快。
正则表达式对象是RegexObject类型,eg:
pattern = re.compile('ab*', re.IGNORECASE)
。反斜杠的麻烦 : 如果要匹配字符串
"\section"
,那么我们需要如下的步骤:- 传递给正则引擎
"\\section"
而不是"\section"
,因为这样引擎才知道pattern中是匹配一个真实存在的\
字符,而不是转义字符s; - 由于Python中字符串字面值的转义规则,所以写下
"\\\\section"
才能给正则引擎传递字符串"\\section"
;
大量重复的反斜杠所生成的字符串也很难懂,因此使用python raw字面值会使得pattern看起来更清晰。
r"\\section"
raw string取消字符串的转义,直接把"\\section"
传递给了正则引擎,这样就清晰多了。- 传递给正则引擎
执行匹配操作的函数:
方法/属性 作用 match() 决定 RE 是否在字符串 刚开始的位置 匹配 search() 扫描字符串,找到这个 RE 匹配的位置 findall() 找到 RE 匹配的所有子串,并把它们作为一个列表返回 finditer() 找到 RE 匹配的所有子串,并把它们作为一个迭代器返回 如果没有匹配到的话,match() 和 search() 将返回 None。如果成功的话,就会返回一个
MatchObject
实例,其中有这次匹配的信息。方法/属性 作用 group() 返回被 RE 匹配的字符串 m.group() == 'tempo'
start() 返回匹配开始的位置 m.start() == 0
end() 返回匹配结束的位置 m.end() == 5
span() 返回一个元组包含匹配 (开始,结束) 的位置 m.span() == (0, 5)
注解
必须注意match()的特殊性,它只会在从 刚开始的位置 就匹配时才匹配成功,也就是说match()成功时的start()一定等于0。
- 对于常用的匹配和替换操作,re模块都提供了模块级的函数来方便使用,
re.search(), re.match(), re.sub()
。对于只使用一次正则表达式,使用模块级函数也许更方便;但对于重复使用的正则表达式,使用编译对象则更有效率。 常用的编译标志:
标志 含义 DOTALL, S 使 . 匹配包括换行在内的所有字符 IGNORECASE, I 使匹配对大小写不敏感 LOCALE, L 做本地化识别(locale-aware)匹配 MULTILINE, M 多行匹配,影响 ^ 和 $ VERBOSE, X 能够使用 REs 的 verbose 状态,使之被组织得更清晰易懂 分组 :
组用 “(” 和 “)” 来指定,并通过
group()、start()、end() 、span()
得到它们匹配文本的开始和结尾索引;组是从 0 开始计数的(左括号对应的编号)。组 0 总是存在;它就是整个 RE 所匹配的文本。
group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
groups() 方法返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
模式中的逆向引用允许你指定先前捕获组的内容:
>>> re.search(r'(\b\w+)\s+\1', 'Paris in the the spring').group() == 'the the'
无捕获组 和 命令组
无捕获组:
(?:...)
,它与普通分组几乎没有区别,但在修改已有组时尤其有用,你可以不用改变所有其他组号的情况下添加一个新组。命名组:命名
(?P<name>...)
、引用(?P=name)
>>> re.search(r'(?P<word>\b\w+)\s+(?P=word)', 'Paris in the the spring').group() == 'the the'
前向界定符 ,又叫 零宽界定符 ,分为前向肯定界定符
(?=...)
和前向否定界定符(?!...)
。注解
前身界定符就是前面提到过的 顺序环视 , 零宽是它们最大的特点 ,意味着他们不会消耗字符。
修改字符串
方法/属性 作用 split() 将字符串在 RE 匹配的地方分片并生成一个列表, sub() 找到 RE 匹配的所有子串,并将其用一个不同的字符串替换 subn() 与 sub() 相同,但返回新的字符串和替换次数 有时,你不仅对定界符之间的文本感兴趣,也需要知道定界符是什么。如果捕获括号在 RE 中使用,那么它们的值也会当作列表的一部分返回:
>>> p = re.compile(r'\W+') >>> p2 = re.compile(r'(\W+)') >>> p.split('This... is a test.') ['This', 'is', 'a', 'test', ''] >>> p2.split('This... is a test.') ['This', '... ', 'is', ' ', 'a', ' ', 'test', '.', '']
常见问题
优先使用字符串的方法;
注意match()只匹配开始的位置;
贪婪 vs 不贪婪:不贪婪的限定符 *?、+?、?? 或 {m,n}?,尽可能匹配小的文本。
>>> print re.match('<.*?>', s).group() # 结果为<html>
用 re.VERBOSE 忽略不在字符类中的空白符,可以使 REs 格式更加干净。
C++ 中的正则表达式¶
C++中常用的2个正则表达式库
- boost 的 regex (C++11引入标准库)
- Google 的 re2( 速度极其快,纯自动机实现 )
<regex> [2]¶
regex 最重要的就是一个类
regex
;两个迭代器regex_iterator, regex_token_iterator
;三个核心函数regex_match(), regex_search(), regex_replace()
regex 类 ,它实际上是basic_regex<char>的typedef,从字符串生成正则表达式对象,同时可以设置正则匹配的属性:
regex reg("Color\\d+",boost::regex::icase|boost::regex::perl);
regex_match() 函数 ,测试 完全匹配 ,注意这里要求必须完全匹配。得到的匹配结果boost::cmatch包含了所有捕获结果(即正则中括号捕获的结果):
std::string str; boost::cmatch mat; boost::regex reg( szReg ); assert(regex_match(str,reg) == true); if(boost::regex_match( szStr, mat, reg)) { //如果匹配成功 //显示所有子串 for(boost::cmatch::iterator itr=mat.begin(); itr!=mat.end(); ++itr) { // 指向子串对应首位置 指向子串对应尾位置 子串内容 LOG(INFO) << itr->first-szStr << ' ' << itr->second-szStr << ' ' << *itr << endl; } }
regex_search() 函数 ,区别在于并不要求完全匹配,可以匹配子字符串,所以从文本中提取特定字符就很好用:
string ss(szStr); boost::smatch mat; boost::regex reg( "\\d+" ); //查找字符串里的数字 std::string::const_iterator start = ss.begin(); std::string::const_iterator end = ss.end(); while(boost::regex_search(start, end, mat, reg)) { // regex_search一次只提取出一个匹配结果 std::cout<< "SEARCHED: " << mat[0] << std::endl; start = mat[0].second; // 需要设置start以跳过当前已经匹配过的部分 }
regex_search()一次只匹配一个匹配项,如果要匹配所有的匹配项,当然可以用上面的循环,但是更好的选择是用regex_token_iterator
regex_replace() 函数 ,文本替换。它在输入数据中进行搜索,查找正则表达式的所有匹配,对于每个匹配,该算法调用match_results::format。 在替换的正则表达式中,$1~$9(或1~9)表示第几个子串,$&表示整个串,$`表示第一个串,$’表示最后未处理的串;(?1~?9新字串)表示把第几个子串替换成新字串:
boost::regex reg( szReg ); string s = boost::regex_replace( string(szStr), reg, "ftp://$2$5"); cout << "ftp site:"<< s << endl; string s1 = "(<)|(>)|(&)"; string s2 = "(?1<)(?2>)(?3&)"; boost::regex reg( s1 ); string s = boost::regex_replace( string("cout << a&b << endl;"), reg, s2, boost::match_default | boost::format_all); cout << "HTML:"<< s << endl; // 输出:HTML: cout << a&b << endl;
regex_iterator 迭代器 ,这个迭代器是访问所有与模式匹配的子串的最佳选择,比循环用regex_search()更好用:
//使用迭代器找出所有数字 boost::regex reg( "\\d+" ); //查找字符串里的数字 boost::cregex_iterator itrBegin = make_regex_iterator(szStr,reg); //(szStr, szStr+strlen(szStr), reg); boost::cregex_iterator itrEnd; for(boost::cregex_iterator itr=itrBegin; itr!=itrEnd; ++itr) { // 指向子串对应首位置 指向子串对应尾位置 子串内容 LOG(INFO) << (*itr)[0].first-szStr << ' ' << (*itr)[0].second-szStr << ' ' << *itr << endl; }
regex_token_iterator 迭代器 ,它和regex_iterator相似,但是它列举的是与正则表达式不匹配的字符序列,该特性对于字符串分割非常有用。 make_regex_token_iterator函数简化regex_token_iterator的构造。 最后的参数用-1表示以reg为分隔标志拆分字符串,如果不是-1则表示取第几个子串,并且可以使用数组来表示同时要取几个子串:
const char *szStr = "http://www.cppprog.com/48.html"; //使用迭代器拆分字符串 boost::regex reg("/"); //按/符拆分字符串 boost::cregex_token_iterator itrBegin = make_regex_token_iterator(szStr,reg,-1); //使用-1参数时拆分 boost::cregex_token_iterator itrEnd; for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr) { cout << *itr << ","; } cout << endl; boost::regex reg("(.)/(.)"); //取/的前一字符和后一字符 int subs[] = {1,2}; // 第一子串和第二子串, 即模式中的2个(.) boost::cregex_token_iterator itrBegin = make_regex_token_iterator(szStr,reg,subs); //使用其它数字时表示取第几个子串,可使用数组取多个串 boost::cregex_token_iterator itrEnd; for(boost::cregex_token_iterator itr=itrBegin; itr!=itrEnd; ++itr) { cout << *itr << ","; }
输出:
http:,,www.cppprog.com,48.html, :,/,m,2,9,1,2,4,
regex_error 异常 ,所有正则表达式的异常都是继承自regex_error的,所以可以通过catch它来捕获所有抛出的异常。
>>> class regex_error : public std::runtime_error {...}
re2 [3]¶
Google的re2号称最快的正则表达式引擎,纯自动机实现,性能超乎想象的高,而且只使用了0.5MB的内存作为空间换时间的开销。 于是在关心性能的时候,re2就成了最佳选择,而且它的功能也一般都能够满足需求。
re2的历史:由Go语言作者之一的Russ Cox开发,这是他发现其它的引擎都是非纯自动机实现,于是就开发了这个纯自动机实现的正则引擎。
RE2主要有 两个主要匹配接口 :
FullMatch
和PartialMatch
,它们的区别在于一个要求 完全匹配 另一个不要求:EXPECT_EQ(RE2::FullMatch("123 hello:1234", "(\\w+):(\\d+)"), false) EXPECT_EQ(RE2::PartialMatch("123 hello:1234", "(\\w+):(\\d+)"), true)
预编译 正则表达式,对于经常使用的Pattern必须预编译好:
RE2 pattern("(\\w+):(\\d+)", RE2::Quiet); assert(RE2::PartialMatch("hello:1234", pattern, &s, &i)); // 成功匹配
re2::RE2::Options 设置正则匹配时的选项,最常用的莫过于大小写敏感选项:
string str = "aaaaa;bbbbb;cccccc;ddddd"; string out; re2::RE2::Options o; o.set_case_sensitive(false); re2::RE2 p0("B+", o); // 由于设置了大小写不敏感,所以这里大写的B也能匹配小写的b re2::RE2::Extract(str, p0, "\\0-\\0", &out); LOG(INFO) << "OUT: " << out; [9637:0523/162211:INFO:image/category_browsing/category/tests/tests.cpp(32)] OUT: bbbbb-bbbbb
re2非常便捷的一点在于,可以在匹配时使用 非常直观的语法进行组的捕获和赋值
int32_t i; string s; // 成功匹配 assert(RE2::PartialMatch("hello:1234", "(\\w+):(\\d+)", &s, &i)); assert(s == "hello"); assert(i == 1234); // 失败:"hello"不能被解析成一个整形 assert(!RE2::PartialMatch("hello:1234", "(\\w+):(\\d+)", &i)); // 成功:跳过NULL参数 assert(RE2::PartialMatch("hello:1234", "(\\w+):(\\d+)", (void*)NULL, &i)); // 失败:整形越界了 assert(!RE2::PartialMatch("hello:123456789123", "(\\w+):(\\d+)", &s, &i));
循环匹配 ,使用
StringPiece
+Consume系列方法
来进行多次匹配:string str = "aaaaa;bbbbb;cccccc;ddddd"; string word; re2::RE2 p1("(\\w+)"); re2::StringPiece input1(str); while (RE2::FindAndConsume(&input1, p1, &word)) { LOG(INFO) << word << " | " << input1; word.clear(); } re2::RE2 p2("(\\w+);"); // 注意这里p2与p1的区别,p2必须把间隔符也带上 re2::StringPiece input2(str); while (RE2::Consume(&input2, p2, &word)) { LOG(INFO) << word << " - " << input2; word.clear(); } [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(32)] aaaaa | ;bbbbb;cccccc;ddddd [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(32)] bbbbb | ;cccccc;ddddd [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(32)] cccccc | ;ddddd [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(32)] ddddd | [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(39)] aaaaa - bbbbb;cccccc;ddddd [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(39)] bbbbb - cccccc;ddddd [7860:0523/152154:INFO:image/category_browsing/category/tests/tests.cpp(39)] cccccc - ddddd
Consume
与FindAndConsume
的区别在于,Consume
必须要求从头匹配。 所以p2正则的后面必须加上;
以使得input2每次后移之后能够从头匹配。因此,对于一般的任务来说,
FindAndConsume
应该更实用一些,它不要求从头匹配。使用 动态参数 ,用于在运行时生成的正则表达式等场景:
const RE2::Arg* args[10]; int n; // running... to generate: pattern, args, n bool match = RE2::FullMatchN(input, pattern, args, n); bool match = RE2::FullMatch(input, pattern, *args[0], *args[1], ..., *args[n - 1]); // equivalent to this
在匹配时还可以 解析不同格式的数字 :8进制、16进制、C风格的数字:
int a, b, c, d; RE2::FullMatch("100 40 0100 0x40", "(.*) (.*) (.*) (.*)", RE2::Octal(&a), RE2::Hex(&b), RE2::CRadix(&c), RE2::CRadix(&d))
这段代码的结果是
a=b=c=d=64
,注意RE2::CRadix
C风格的数字表示法,即以0开头是8进制,0x开头是16进制,一般为10进制,这在计算机中很常用。替换操作 : new_str中可以使用
\0, \1, ..., \n
来捕获p中出现的组。Relpace(in, p, new_str)
只替换第一次出现的子串GlobalReplace(in, p, new_str)
全局替换所有出现的子串Extract(in, p, new_str, &out)
替换第一次出现的子串,并用新模式进行替换后复制到out,这个方法的有用之处在于在new_str中使用\0, \1, ..., \n
来捕获p中出现的组。
QuoteMeta(str)
对str中的 正则元字符进行转义 。太棒了!这样的话就可以不用写一个难懂的、充满了转义符的正则表达式字符串,用这个函数优美的解决!
脚注
[1] | Python 中的正则表达式参考 此文 |
[2] | boost regex reference |
[3] | re2的 项目主页 ,以及它所支持的 语法 |