Ruby对正则表达式支持非常好(built-in support),下面我将近段时间学习的Ruby做个总结,包括
- Ruby中正则表达式可以做什么
- 正则表达式的用法,包括匹配的方法,替换,分组匹配等
- 使用中需要注意的地方
- 学习资源。
本文中,为了便于描述,将一个正则表达式称为一个模式(pattern)。
Ruby中正则表达式定义
就跟Ruby其他对象一样,正则表达式也是Ruby中的一个对象,是Regexp类的实例。因此它可以被当作参数进行传递,这更便于其在Ruby中的应用。
正则表达式的定义
主要有三种定义方式//, %r、Regexp.new()内。这三种效果相同,实质都是新建了一个Regexp的类,如定义/cat/, %r{cat},Regexp.new(“cat”),都是匹配包含cat的字符串,不过它们之间也有区别
- 在//之间,要进行转义
- 在%r{}内,不用进行转义
- Regexp.new()内,不用进行转义
/mm\/dd/,Regexp.new(“mm/dd”),%r{mm/dd}三者效果相同,而且这三种定义方式都可以加入任意的Ruby的表达式(expressions)#{}
|
|
在使用正则表达式时,主要是用//语法,因为它让我们很迅速的写出一个正则表达式。但如果某个模式中需要进行大量的转义,我们可以通过%r语法也可以给我们比较便利的定义一个正则表达式。而标准的Regexp.new的定义方法相对比较麻烦,不符合ruby简介方便的特点。
Regexp.new方法的参数,除可以传递字符串之外,可以传入Regexp对象(Regexp对象的options也会被一起传入),当然new方法中还包含其他参数,主要是指定他的options
|
|
options选项是对正则表达式的功能的扩充,其中/i(IGNORECASE)代表忽略模式和待匹配字符串的大小写问题,/x(EXTENDED)代表忽略正则中的空格,因为复杂的正则表达式比较难阅读,我们可以通过空格和换行对其进行分割,而且在新行的尾端还可以进行注释#commnet,方便阅读。注意这里是忽略模式中空格,不是待匹配字符串中的空格。/m(MULTILINE)主要是可以使’.’匹配到’\n’,正常情况下,’.’可以匹配除’\n’外的所有字符. /o表示仅仅执行一次#{}。关于编码的问题,后续补充
如下:
|
|
options是对正则表达式功能的扩展,探其究竟,options选项实际是枚举类型,用一个二进制位代表一个options,因此可以通过|混合使用,比如“Regexp::IGNORECASE | Regexp::MULTILINE”,实际对应的就是/mi,其值为m与i对应值的和. /cat/默认情况options为nil,即/cat/.options 输出为0,Regexp.new方法中含有参数true对应Regexp::IGNORECASE,
|
|
Regexp中还包含其他API,比如判断二个exp是否相等,/abc/ == /abc/x,结果为false,=== 可以判断与字符串是否全匹配,输出false or true .
|
|
其他的API这里不做介绍,有兴趣的可以看这里 列出了所有Method方法。至此我们已经知道exp的定义方法,具体用法会在下文中描述。
正则表达式作用
- 测试字符串是否符合某个模式
- 对字符串根据模式进行拆分(提取字符串中符合模式的部分)
- 根据模式匹配结果对字符串进行替换
Ruby中正则表达式用法
在上文中已经讲解了正则表达式的定义,本节主要说下正则表达的实际使用,即如何进行匹配及匹配返回结果如何使用。
正则表达式匹配结果
为了在下一小节“如何匹配”更好的理解,我把这一部分提前,不过基于第一部分的讲解,理解也没有难度。
=~
=~肯定匹配, !~否定匹配。=~表达式返回匹配到的位置索引,失败返回nil,所以我们可以把匹配结果当作if条件(在Ruby中if(0)被视为true,只有nil和false才当做失败,再一次说明Ruby对正则的支持),⚠️:符号左右内容可交换
通常我们可以需要()进行分组,它会存储匹配的结果,变量的数量与圆括号的对数相等,在模式内,\1代表第一个园括号,\n对表第n个园括号,在模式外,通过$n可以获得第n个园括号的匹配结果。⚠️:括号嵌套的情况
通过=~方法,我们只可以取得匹配的索引位置,对于”begin7catend” =~ /\d/,我们不能通过方便获得匹配到的结果(此处指7,假设我们后续需要继续使用它)”。虽然我们可以通过分组的方法获得(见上文),但对于简单的正则表达式而言,我们或许不需要使用分组方法,可通过系统变量$&获得匹配结果,除此之外, $`返回匹配前内容, $’ 返回匹配后内容,这样书写不便于阅读,因此match方法提供更友好的API名称
match方法
- Ruby中正则表达式还给我们提供了match方法,便于将我们匹配的结果输出。
regexp#match(str),返回MatchData对象,一个数组,从0开始,长度为匹配的数量(注意含有圆括号的情况)。还有match.pre_match返回匹配前内容,match.post_match返回匹配后内容,如果匹配失败返回nil
- 我们同样可以通过系统变量$&、$’,$`
String中的scan方法
regexp#match()只能匹配一次,如果想匹配所有要用可以使用String中的scan方法,它会返回Array,scan(pattern) → array 或者后接一个block:scan(pattern) {|match, …| block }
|
|
如何进行匹配
这部分不是讲匹配背后的原理(匹配原理,中文传送门),而是介绍如何高效正确地书写正则表达式。首先列一下常用reference。
几点说明:
- 1、正则表达式支持Range,不仅可以利用模式中的Range,也可以利用字符串中Range,如代码所示,可以匹配输入行的开始和结尾
- 2、如果要匹配下面的字符,需要进行转义 : | ( ) [ ] { } + \ ^ $ * ?
3、可以对上表进行如下分类:
- 锚点(Anchor)^(表示字符串开始)$\A\Z\z\B\b :\Z matches the end of a string unless the string ends with \n, in which case it matches just before the \n.
- 字符匹配的类型:[]、[^]以及上图中第二列的部分,这里还有一个The POSIX character classes和字符编码的问题,稍后补齐
- 重复匹配:主要是第三列,较高优先级/AB+/是匹配含有字符A后接不少于一个B的字符串(ABBBB),而不是匹配含有不少于一个(AB)的字符串(ABABAB)当然这就有一个匹配次数的问题,具体见下文的“贪婪匹配vs懒惰匹配”
- 交替(Alternation):| 低优先级
- 分组:(),分组为正则表达式提供更丰富的组合。关于分组的情况,在下面会有详细介绍。
- 锚点(Anchor)^(表示字符串开始)$\A\Z\z\B\b :\Z matches the end of a string unless the string ends with \n, in which case it matches just before the \n.
|
|
字符串的替换
- 很多时候匹配是为了替换,Ruby中进行正则替换非常简单,两个方法即可搞定,sub()+gsub()。sub只替换第一次匹配,gsub(g:global)会替换所有的匹配,没有匹配到返回原字符串的copy
- 如果想修改原始字符串用sub!()和gsub!(),没有匹配到返回nil。
- sub或者gsub方法参数除了可以传入替换的字符串之外,还可以接block,对匹配的字符串进行block内的操作
- 除此之外, 还有一种惯用的替换方法(Ruby 1.9以上), 通过hash方式替换
|
|
分组匹配
- 分组的一个很重要的应用是查找不同形式的重复,对于分组,我们会根据园括号对数进行编号,从1开始,通过\n就可以引用第n个括号所匹配的结果,从而达到匹配重复模式的效果,其中n的数量与括号的数量应该是一致的,大家可以试下引用\n+1的会出现什么情况,当然还有括号套括号的情况对于数字的变化,会更佳有益于对\n的理解。
- 我们也可以给某个分组命令,通过名字而不是数字进行匹配,会更便于理解。命名方式如下:( ?pattern )引用方式为 \k (or \k’name’).
- ⚠️当然使用=~进行匹配时,且模式在=~左边时,匹配名可以当做局部变量进行使用。
- 命名匹配的方式同样适应于字符串的替换12345678910111213141516171819202122232425# 理解\n的含义,p /(\w)(\w)\1\2/.match('He said "hehellllo"') #=>#<MatchData "hehe" 1:"h" 2:"e">p /(\w)(\w)\1\2/.match('He said "Hehellllo"') #=> #<MatchData "llll" 1:"l" 2:"l"># match duplicated lettershow_regexp('He said "Hello"', /(\w)\1/) # => He said "He->ll<-o"# match duplicated substringsshow_regexp('Mississippi', /(\w+)\1/) # => M->ississ<-ippi#using name# match duplicated letterstr = 'He said "Hello"'show_regexp(str, /(?<char>\w)\k<char>/) # => He said "He->ll<-o"# match duplicated adjacent substringsstr = 'Mississippi'show_regexp(str, /(?<seq>\w+)\k'seq'/) # => M->ississ<-ippi/(?<hour>\d\d):(?<min>\d\d)(..)/ =~ "12:50am" # => 0"Hour is #{hour}, minute #{min}" # => "Hour is 12, minute 50"# You can mix named and position-based references"Hour is #{hour}, minute #{$2}" # => "Hour is 12, minute 50""Hour is #{$1}, minute #{min}" # => "Hour is 12, minute 50"puts "fred:smith".sub(/(\w+):(\w+)/, '\2, \1') #=>smith, fredputs "nercpyitno".gsub(/(?<c1>.)(?<c2>.)/, '\k<c2>\k<c1>') #=> encryption
贪婪匹配vs懒惰匹配
这两种匹配属于标准正则表达式内容,与Ruby没关,但新手如果不明白匹配时会发生莫名其妙的错误,所以特别总结一下。
- 贪婪匹配:尽可能多匹配,正则默认是贪婪匹配。例子:a.*b它将会匹配最长的以a开始,以b结束的字符串。对于aabab的匹配结果是aabab。
- 懒惰匹配:尽可能少匹配。例子:a.*?b对于aabab的匹配结果是aab和ab。
一般是在原来表达式结尾加?就由贪婪匹配变成了懒惰匹配。常用的懒惰限定符有(去年最后的问题就是贪婪匹配):- ?重复任意次,但尽可能少重复
- +?重复1次或更多次,但尽可能少重复
- ??重复0次或1次,但尽可能少重复
- {n,m}?重复n到m次,但尽可能少重复
- {n,}?重复n次以上,但尽可能少重复
后续补充
1、 backtracking
2、Regular Expression Extensions
3、Backreferences and Named Matches
参考
http://ruby-doc.org/core-2.1.1/Regexp.html
http://ruby-doc.org/core-2.1.2/String.html#method-i-scan
regexp.rb
Programming Ruby 4th《第7章》