加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

正则表达式 Regular Expression

发布时间:2020-12-13 19:53:39 所属栏目:百科 来源:网络整理
导读:http://www.cnblogs.com/lovewife/articles/1438417.html 字符的表示 1. 普通字符,特殊字符: 特殊字符:.|*?+(){}[]^$,相当于语言的关键字,这些字符前面加转义符""表示字符本身,否则就作为正则表达式特殊用途字符。 特殊转义字符:下表主要针对.Net的

http://www.cnblogs.com/lovewife/articles/1438417.html

字符的表示
1. 普通字符,特殊字符:
特殊字符:.|*?+(){}[]^$,相当于语言的关键字,这些字符前面加转义符""表示字符本身,否则就作为正则表达式特殊用途字符。
特殊转义字符:下表主要针对.Net的正则表达式

Escape sequence Character code Meaning
a 0x07 响铃字符
f 0x0C Form feed.
n 0x0A 换行符
r 0x0D 回车符
t 0x09 Tab character.
v 0x0B Vertical tab.
e 0x1B ASCII的Esc字符
b 0x08 1. 在[]里面出现时表示删除键的ASCII字符(Backspace)
2. 不是出现在[]里面,则表示单词的边界,例如表达式bw+b匹配文本串"Hi <strong>Hello Kitty</strong>的结果是:"Hi","strong","Hello","Kitty","strong"。
dd 0dd An octal character code,where dd is one or more octal digits.
xXX 0xXX A hexadecimal character code,where XX is one or more hexadecimal digits.
u0020
Matches a Unicode character using hexadecimal representation (exactly four digits).
cZ Ctl+Z Matches an ASCII control character; for example,cC is control-C.
除了上面的特殊字符之外,其它普通字符都直接去匹配输入文本串中的字符。
2. 字符枚举:中括号括起来,例如[abc]表示出现a,b,c中任意一个字符都可以;[^abc]则匹配除了a,c之外的任意一个字符。
3. 字符范围:[a-zA-Z0-9]。
4. 综合表示:
w
所有大小写英文字母、数字字符、下划线,等同于[a-zA-Z0-9_]
W
除了w之外的所有字符,等同于[^a-zA-Z0-9_],包括空白字符、各种特殊字符等
s 所有空白字符,等同于[tnrfv]
S 除了s之外的所有字符,等同于[^tnrfv]
d 所有的数字字符,等同于[0-9]
D 所有的非数字字符,等同于[^0-9]
A 匹配字符串的开始位置(不代表任何字符,匹配结果长度为0)
Z 匹配字符串的结束位置(不代表任何字符,匹配结果长度为0)
. 在DOTALL模式下,表示任何字符(包括空白字符、各种特殊字符等),等同于[wW]、[sS]、[dD]等
在非DOTALL模式下,表示除了换行符之外的所有字符
DOTALL模式,参考下面正则表达式选项部分

Alternation,Condition Constructs(等价、条件式结构)
1. Alternation Construct: (pattern1)|(pattern2),子表达式1或者2任意一个匹配就匹配成功。
2. Condition Construct: (?(expression)(patternYes)|(patternNo)),如果符合expression,则使用patternYes子表达式进行匹配,否则使用patternNo进行匹配。expression也可以是backreference中的group名字,backreference后面详细讲述。这个语法形式是.Net正则表达式的,其它正则表达式引擎也有支持条件式结构的,但语法可能不一样。
例如正则式(?(d{4})((19|20)d{2})|(d{2}))匹配文本串"2004年 98年",结果是"2004","98",其中"2004"是使用patternYes部分匹配出来的,而"98"是使用patternNo部分。

Quantifier(量词)
指表达式需要重复匹配多少次。
* 0或多次
+ 1或多次
? 作为量词时表示0或1次,在其它量词后面出现时作为greedy,lazy/non-greedy模式开关。
{n} n次
{n,} 最少n次
{n,m} 最少n次,最多m次
*?,+?,{n}?,{n,}?,m}? 开启lazy/non-greedy模式,例如{n,m}表示最少n次,最多m次,在这个范围内尽可能少的匹配。
greedy,lazy/non-greedy模式参考下面相关部分。

Zero-Width Assertions(零宽度断言)
这种结构产生的匹配结果长度为0(所以称作零宽度),只是用于对上下文环境做判断(所以称作断言)。
^ 如果使用Multiline选项,匹配每一行的开始位置;如果使用Singleline选项,匹配整个字符串开始位置。^如果出现在[]中就不是Zero-Width Assertion了。
$ 如果使用Multiline选项,匹配每一行的结束位置(n之前);如果使用Singleline选项,匹配整个字符串结束位置。
A 忽略Multiline选项(相当于取消Multiline选项并设置Singleline选项),匹配整个字符串开始位置。
Z 忽略Multiline选项,匹配整个字符串结束位置,中间的n不会考虑。.Net中z和Z的效果完全一样,不知道是bug还是怎么回事。
前面提到的条件式结构,以及b如果不是出现在[]中,都是Zero-Width Assertions;后面将会讲到的lookahead,lookbehind也是一种Zero-Width Assertion。
Multiline,Singleline等,参考正则表达式选项部分。

Greedy,Lazy/Non-greedy(贪婪模式,惰性模式)
在使用quantifier量词修饰,需要执行重复n次的匹配中,greedy模式尽可能多的匹配更多字符,而lazy/non-greedy模式则尽可能少的匹配字符。
NFA(参考后面NFA,DFA部分)默认都是采用greedy模式,而当今主要正则表达式引擎都是NFA,因此注意greedy模式的影响。将greedy模式改为non-greedy,在量词修饰符后面添加"?"实现。例如NFA中w+为greedy模式,w+?为non-greedy模式。

文本串为:"dxxxdxxxd",greedy模式:
Regular Expression Result
(d)(w+) "w+"将匹配第一个"d"之后的所有字符"xxxdxxxd"
(d)(w+)(d) "w+"将匹配第一个"d"和最后一个"d"之间的所有字符"xxxdxxx"。虽然"w+"也能够匹配上最后一个"d",但是为了使整个表达式匹配成功,"w+"可以"让出"它本来能够匹配的最后一个"d"

文本串为:"dxxxdxxxd",non-greedy模式:
Regular Expression Result
(d)(w+?) "w+?"将尽可能少的匹配第一个"d"之后的字符,结果是:"w+?"只匹配了一个"x"(第二个字符)
(d)(w+?)(d) 为了让整个表达式匹配成功,"w+?"不得不匹配"xxx"才可以让后边的"d"匹配,从而使整个表达式匹配成功。因此,结果是:"w+?"匹配"xxx"

Group,Back Reference(分组、反向引用)
正则表达式引擎不仅记录最终的匹配结果,使用()括起来的子表达式匹配到的文本串,在匹配过程中也会记录下来。在.Net中,可以使用Match.Groups访问某个匹配结果的所有分组。
没有指定名称的分组称为匿名分组,对所有的匿名分组都默认有一个组号。.Net中组号为0的分组是整个正则表达式,而不管这个正则表达式是否使用()括起来了。对于其它的匿名分组,根据左括号出现的先后位置依次从1开始编号。例如表达式((abc)d+)?(xyz)(.*)总共有5个分组,依次为0:((abc)d+)?(xyz)(.*),1:((abc)d+),2:(abc),3:(xyz),4:(.*)。
可以对分组命名,.Net中命名方法:(?<groupName>patterns)。如果存在命名分组,则所有命名分组的编号将从最后一个匿名分组的位置开始,依次递增。例如表达式((?<group1>abc)d+)?(?<group2>xyz)(.*)的5个分组依次为0:((?<group1>abc)d+)?(?<group2>xyz)(.*),1:((?<group1>abc)d+),2:(.*),3:(?<group1>abc),4:(?<group2>xyz)。

在表达式的后面部分引用前面的某一个子表达式分组叫做反向引用。
对于匿名分组的反向引用方法是"" 加上分组编号,对于命名分组,.Net中的引用方法是k<groupName>。
Regular Expression Input String Result
('|")(.*?)(1) 'hello' "world" 1. 'hello'
2. "world"
(w)1{4,} aa bbbb abcdefg ccccc 111121111 999999999 1. ccccc
2. 99999999
<(?<tag1>w+)>[wW]*</k<tag1>> <strong>Hello Kitty</strong> is the name <strong>Hello Kitty</strong>
注意上面表格中第二个例子的表达式与w{5,}的区别,(w)1{4,}表示同一个字符重复至少5次以上,而w{5,}表示连续5个以上字符(不必是同一个字符)。

可以禁止正则表达式记录某个分组的匹配结果,这样的分组也就不会被编号,无法被反向引用。禁止分组使用(?:patterns),例如表达式abc(?:w{3})(d+)abc总共有两个分组,组号0为整个表达式,组号1为(d+)。
.Net中,1到9总是被当作反向引用;12这种类型,如果存在编号为12的分组,则作为反向引用,否则将12转义为ASCII字符,为了消除这种歧意,可以使用k<n>这种方式。
注意:.Net中分组命名时尖括号<,>可以使用单引号代替。

Lookahead,Lookbehind(正向预搜索、反向预搜索)
匹配当前的某一个子表达式时,可能需要根据前面或者后面出现的字符进行判断(即上下文环境),lookahead、lookbehind就是用于这个目的。
NFA以文本串作为有穷输入字符集Σ,从文本串逐个读取字符进行匹配,所以沿着字符读取方向的是lookahead,与字符读取方向相反则为lookbehind。
lookahead: (?=patterns),(?!patterns)。lookbehind: (?<=patterns),(?<!patterns)。

Regular Expression Input String Result
lookahead Windows (?=NT|XP) Windows 98,Windows NT,Windows 2000 仅匹配"Windows NT"中的"Windows "
解释:匹配"Windows ",后面必须是字符"NT"或者"XP"
lookahead (w)((?=111)(1))+ aaa ffffff 999999999 匹配6个"f"的前4个以及9个"9"的前7个
解释:以6个f的地方为例,第一个f匹配(w)。接下来是一个子表达式((?=111)(1)),需要出现1次以上。这个子表达式由两个部分组成,第一部分(?=111)是一个lookahead,可以把它跟最后那个(1)放在在一起考虑,所以对于第4个f,能够匹配(1),也满足它的前面一个位置(第三个f)后面还有三个f(第4、5、6三个)
lookahead do(?!w) done,do,dog 只匹配"do,"这个位置上的"do"
解释:匹配"do",后面不能有任何[a-zA-Z0-9_]这些字符
lookbehind (?<=d{4})d+(?=d{4}) 1234567890123456 "56789012"

Options(正则表达式选项)
JavaScript的正则表达式,使用/gi这样的开关控制正则表达式选项 。.Net中可以使用RegexOptions枚举进行全局设置,可以在分组表达式中使用(?imnsx-imnsx:patterns)方式,在这个分组内开启或禁用某些选项,也可以在表达式的中间使用(?imnsx-imnsx),从中间这个位置开始开启或禁用某些选项。全局RegexOptions的优先级低于嵌入方式。
嵌入方式中imnsx表示打开某种选项或选项的组合,前面添加减号"-"表示关闭这些选项。例如(?ix-ms)表示从这个位置开始,打开IgnoreCase、IgnorePatternWhiteSpace选项,关闭Multiline、Singleline选项。
嵌入方式修改正则表达式选项,也叫做Modifier
RegexOption member Inline character Description
None N/A Specifies that no options are set.
IgnoreCase i 匹配过程中忽略大小写因素
Multiline m 在Multiline模式下,^和$分别匹配每一行的开始和结束位置;否则将分别匹配整个文本串的开始和结束位置
ExplicitCapture n 匹配过程中不捕获任何匿名分组,相当于在表达式中对所有匿名分组使用(?:)
Compiled N/A 将正则表达式预编译到assembly中,提高匹配性能
Singleline s 也就是DOTALL模式的开关,打开Sigleline开关,.将匹配任何一个字符,否则.只匹配换行符以外的字符
IgnorePatternWhitespace x 忽略表达式中没有转义的空白字符(s),并开启单个未#符号的注释方式
这个选项开启后,表达式中从未转义的#符号开始,到这一行的结束位置都作为注释对待
表达式中另外一种注释方式为(#your comments),只有括号里面的部分才是注释内容
RightToLeft N/A 对输入字符集的默认扫描方向为从左至右,该选项将扫描方向修改为从右至左
该选项只是改变了对输入字符集的扫描方向,它并不会改变表达式对子文本串的匹配方向,lookahead、 lookbehind的方向也不会改变,lookahead仍然向右搜索,lookbehind仍然向左搜索
ECMAScript N/A ...
CultureInvariant N/A ...

NFA,DFA
正则表达式引擎的两种类型,NFA: Nondeterministic Finite Automata,DFA: Deterministic Finite Automaton。
NFA基于正则表达式去匹配文本(文本作为有穷字母表Σ),而DFA是基于文本去匹配正则表达式。DFA捏着文本串去比较正则式,看到一个子正则式,就把可能的匹配串全标注出来,然后再看正则式的下一个部分,根据新的匹配结果更新标注。而NFA是捏着正则式去比文本,吃掉一个字符,就把它跟正则式比较,匹配就记下来,然后接着往下干。一旦不匹配,就把刚吃的这个字符吐出来,一个个的吐,直到回到上一次匹配的地方。把多吃的字符吐出部分重新匹配的过程叫做backtracking(回溯)。
.Net中可以使用(?>patterns),禁止对这个子表达式进行回溯,即对输入字符backtracking过程中,一旦遇到这个子表达式已经匹配的字符,就停止backtracking。下面示例演示了这个效果:
Regular Expression Input String Result
(w)(1*)(a) aaa ffffff 999999a999 1. aaa
2. 999999a
(w)(?>1*)(a) aaa ffffff 999999a999 只有999999a
因为在对aaa的匹配过程中,最后一个a被1*匹配上,但又不允许回溯,所以在读取aaa后面的那个空格字符后,发现跟子正则式(a)不匹配

NFA、DFA主要对比:
1. DFA对文本串只扫描一次,速度快(时间与有穷字母表Σ的大小成线性关系),但特性少;NFA需反复扫描文本串,速度慢,但特性多。目前主要正则表达式引擎基本都是NFA,例如Perl、Java、.Net、Python、Td、Emacs,DFA的引擎有awk、egrep、lex。
2. NFA最左子正则式优先匹配,DFA是最长左子正则式优先匹配。
3. 只有NFA支持lazy、backtracking、backreference,NFA缺省使用greedy模式,NFA可能陷入递归陷阱导致性能极差。DFA只包含有穷状态,匹配过程中无法捕获子表达式(分组)的匹配结果,因此也无法支持backreference。
有另一种NFA引擎,叫做POSIX NFA引擎。传统NFA在backtracking时,只要当前位置上的最左子正则式匹配成功就停止;而POSIX NFA会继续尝试backtracking,以试图像DFA一样找到最长左子正则式。因此POSIX NFA速度更慢。
Engine Regular Expression Input String Result
NFA perl|perlman perlman book perl
NFA perlman|perl perlman book perlman
DFA perl|perlman perlman book perlman

详细的NFA、DFA定义、算法,参考编译原理。

附加说明 1. 正则表达式在发展过程中,形成了很多版本的引擎,最基本的为grep,为了使grep具备更多特性而扩展出egrep, 目前使用的大多数正则引擎都是backtracking型的NFA。不同的正则表达式引擎之间,实现上多少也都有些差别,并且开发商还可能作出特有的扩展、语法形式等。因此,这就意味着并不是同一个正则表达式就会适用于所有的环境,例如.Net中的正则表达式,就不一定能在Java、Python、Unix中工作,这在网上查找正则表达式资源时需要注意。 2. 使用传统NFA,书写正则表达式需要特别注意性能问题,否则很容易导致死循环、性能极差等情况。 对正则表达式依赖性比较强的系统(大量使用正则做搜索匹配),最好完全掌握NFA->DFA算法,充分理解所使用的正则表达式引擎的原理和特性。 可以通过减少表达式中的模糊匹配、限制回溯等方法,将传统NFA的性能从多项式优化到线形关系,这完全取决于正则式的写法。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读