Ruby Regex 教程:从入门到精通
Ruby Regex 教程:从入门到精通
正则表达式(Regular Expression,简称 Regex 或 Regexp)是处理字符串的强大工具。它使用一系列特殊字符来定义搜索模式,让你能够轻松地匹配、查找、替换和验证文本。Ruby 提供了强大的正则表达式引擎,使得在 Ruby 中处理文本变得高效而简洁。本教程将带你从零开始,逐步掌握 Ruby 正则表达式的精髓。
1. 正则表达式基础
1.1 什么是正则表达式?
正则表达式本质上是一个描述字符串模式的字符串。这个模式由普通字符(例如字母、数字)和特殊字符(元字符)组成。元字符具有特殊的含义,用于构建复杂的匹配规则。
1.2 Ruby 中的正则表达式
在 Ruby 中,正则表达式通常被包含在两个斜杠 /
之间,或者使用 %r{}
语法创建:
ruby
/pattern/
%r{pattern}
1.3 简单的匹配
最基本的正则表达式就是直接匹配字符。例如,/ruby/
将匹配字符串中出现的 "ruby":
ruby
"I love ruby" =~ /ruby/ # 返回 7 (匹配开始的位置)
"I love Ruby" =~ /ruby/ # 返回 nil (区分大小写)
=~
运算符用于测试字符串是否与正则表达式匹配。如果匹配,它返回匹配开始的索引;如果不匹配,则返回 nil
。
1.4 修饰符
正则表达式可以带有修饰符,用于改变匹配的行为。常用的修饰符包括:
i
:忽略大小写m
:多行模式(.
匹配换行符)x
:扩展模式(忽略空白字符和注释)o
: 只执行一次插值
```ruby
"I love Ruby" =~ /ruby/i # 返回 7 (忽略大小写)
"line1\nline2" =~ /line.line/ # 返回 nil (. 不匹配换行符)
"line1\nline2" =~ /line.line/m # 返回 0 (. 匹配换行符)
"abc # comment" =~ /abc/x #返回0
```
2. 元字符
元字符是正则表达式的构建块,它们具有特殊的含义。
2.1 常用元字符
| 元字符 | 描述 | 示例 |
| ------ | ------------------------------------------------------------ | ------------------------------------------ |
| .
| 匹配除换行符外的任意单个字符 | /a.c/
匹配 "abc", "a1c", "a&c" 等 |
| *
| 匹配前面的字符零次或多次 | /ab*/
匹配 "a", "ab", "abb", "abbb" 等 |
| +
| 匹配前面的字符一次或多次 | /ab+/
匹配 "ab", "abb", "abbb" 等 |
| ?
| 匹配前面的字符零次或一次 | /ab?/
匹配 "a", "ab" |
| {n}
| 匹配前面的字符恰好 n 次 | /a{3}/
匹配 "aaa" |
| {n,}
| 匹配前面的字符至少 n 次 | /a{2,}/
匹配 "aa", "aaa", "aaaa" 等 |
| {n,m}
| 匹配前面的字符 n 到 m 次 | /a{2,4}/
匹配 "aa", "aaa", "aaaa" |
| []
| 字符类,匹配方括号内的任意一个字符 | /[abc]/
匹配 "a", "b", "c" |
| [^]
| 否定字符类,匹配不在方括号内的任意一个字符 | /[^abc]/
匹配除 "a", "b", "c" 外的任意字符 |
| ^
| 匹配字符串的开头 | /^abc/
匹配以 "abc" 开头的字符串 |
| $
| 匹配字符串的结尾 | /abc$/
匹配以 "abc" 结尾的字符串 |
| \
| 转义字符,用于转义元字符或创建特殊字符序列 | /\./
匹配 ".",而不是任意字符 |
| |
| 或,匹配 |
两侧的任意一个表达式 | /a|b/
匹配 "a" 或 "b" |
| ()
| 分组,将括号内的表达式作为一个整体,并可以捕获匹配的内容 | /(ab)+/
匹配 "ab", "abab", "ababab" 等 |
2.2 特殊字符序列
| 序列 | 描述 |
| ------ | ---------------------------------- |
| \d
| 匹配任意一个数字,等价于 [0-9]
|
| \D
| 匹配任意一个非数字字符,等价于 [^0-9]
|
| \w
| 匹配任意一个字母、数字或下划线,等价于 [a-zA-Z0-9_]
|
| \W
| 匹配任意一个非字母、数字或下划线的字符,等价于 [^a-zA-Z0-9_]
|
| \s
| 匹配任意一个空白字符(空格、制表符、换行符等) |
| \S
| 匹配任意一个非空白字符 |
| \b
| 匹配单词边界 |
|\A
| 字符串开头 |
|\z
| 字符串结尾 |
|\Z
| 字符串结尾(如果存在结尾换行符,则匹配换行符之前的位置) |
3. Ruby 正则表达式方法
Ruby 提供了多个方法来使用正则表达式。
3.1 match
match
方法返回一个 MatchData
对象,其中包含有关匹配的详细信息:
```ruby
match_data = "Hello, 123 world!".match(/(\w+), (\d+) (\w+)/)
if match_data
puts "Full match: #{match_data[0]}" # Full match: Hello, 123 world
puts "Group 1: #{match_data[1]}" # Group 1: Hello
puts "Group 2: #{match_data[2]}" # Group 2: 123
puts "Group 3: #{match_data[3]}" # Group 3: world
puts "Groups: #{match_data.captures}" #捕获的数组
else
puts "No match"
end
```
如果没有匹配,match
返回 nil
。
3.2 scan
scan
方法返回一个数组,其中包含所有与正则表达式匹配的子字符串:
ruby
"123-456-789".scan(/\d+/) # => ["123", "456", "789"]
"123-456-789".scan(/(\d+)-(\d+)-(\d+)/) # => [["123", "456", "789"]]
如果正则表达式中包含捕获分组,scan
会返回一个二维数组
3.3 gsub
和 sub
gsub
(全局替换)和 sub
(单次替换)方法用于替换字符串中与正则表达式匹配的部分:
```ruby
"hello world".gsub(/l/, "L") # => "heLLo worLd" (替换所有 "l")
"hello world".sub(/l/, "L") # => "heLlo world" (只替换第一个 "l")
使用捕获分组
"John,Doe".gsub(/(\w+),(\w+)/, '\2, \1') # => "Doe, John"
```
3.4 split
split
方法可以使用正则表达式来分割字符串
ruby
"apple, banana; orange".split(/[,;]\s*/) # => ["apple", "banana", "orange"]
3.5 =~
和 !~
=~
前面已经介绍过,用于测试匹配并返回索引。!~
是 =~
的否定版本,如果正则表达式不匹配字符串,则返回 true
,否则返回 false
:
```ruby
"hello" =~ /llo/ # => 2
"hello" !~ /llo/ # => false
"hello" =~ /abc/ # => nil
"hello" !~ /abc/ # => true
```
4. 高级技巧
4.1 贪婪与非贪婪
默认情况下,量词(*
、+
、{n,m}
)是“贪婪的”,它们会尽可能多地匹配字符。在量词后面加上 ?
可以使其变为“非贪婪”,只匹配最少的字符:
ruby
"<a>foo</a><b>bar</b>".match(/<.*>/)[0] # => "<a>foo</a><b>bar</b>" (贪婪)
"<a>foo</a><b>bar</b>".match(/<.*?>/)[0] # => "<a>" (非贪婪)
4.2 零宽断言 (Zero-Width Assertions)
零宽断言用于指定一个位置,该位置的前面或后面必须满足(或不满足)某种条件,但断言本身不匹配任何字符。
- 正向先行断言
(?=...)
:指定位置后面必须匹配...
- 负向先行断言
(?!...)
:指定位置后面不能匹配...
- 正向后行断言
(?<=...)
:指定位置前面必须匹配...
- 负向后行断言
(?<!...)
:指定位置前面不能匹配...
```ruby
匹配后面跟着 "bar" 的 "foo"
"foobar foobaz".scan(/foo(?=bar)/) # => ["foo"]
匹配前面是 "foo" 的 "bar"
"foobar foobaz".scan(/(?<=foo)bar/) # => ["bar"]
```
注意:Ruby 不完全支持可变长度的后行断言。
4.3 命名捕获分组
可以使用 (?<name>...)
语法为捕获分组命名,然后通过名称访问它们:
ruby
match_data = "John Doe".match(/(?<first_name>\w+) (?<last_name>\w+)/)
puts match_data[:first_name] # => "John"
puts match_data[:last_name] # => "Doe"
4.4 条件匹配
Ruby 支持在正则表达式中使用条件表达式。
语法为(?(condition)yes-pattern|no-pattern)
```ruby
regex = /(()?\d{3}(?(1))|-)\d{3}-\d{4}/
"555-123-4567".match(regex) # => #
"(555)123-4567".match(regex) # => #
"555123-4567".match(regex) # => nil
``
(1)
这个例子检查电话号码是否有括号。如果找到了开括号,则条件为真,
yes-pattern要求闭括号
),否则
no-pattern使用
-`。
5. 正则表达式的性能
虽然正则表达式非常强大,但不当使用也可能导致性能问题。以下是一些优化建议:
- 避免不必要的回溯:复杂的正则表达式可能导致大量的回溯,降低匹配速度。尽量使正则表达式简洁明了。
- 使用更具体的字符类:例如,使用
\d
代替.
来匹配数字。 - 预编译正则表达式:如果需要多次使用同一个正则表达式,可以使用
Regexp.new
创建一个正则表达式对象,并将其存储起来,避免重复编译。 - 谨慎使用贪婪量词:在确保正确性的前提下,尽量使用非贪婪量词。
更进一步:掌握核心
本教程涵盖了 Ruby 正则表达式的方方面面,从基础的字符匹配到高级的零宽断言和条件匹配。 通过学习和实践,你现在已经具备了使用 Ruby 正则表达式处理各种文本问题的能力。
要成为真正的正则表达式专家,还需要不断地练习和探索。 建议你:
- 阅读 Ruby 文档:Ruby 的官方文档提供了有关正则表达式的详细信息。
- 在线测试工具:使用像 Rubular (rubular.com) 这样的在线工具测试和调试你的正则表达式。
- 分析实际案例:学习别人是如何使用正则表达式解决实际问题的。
- 挑战自我:尝试解决一些复杂的文本处理任务,不断提高你的正则表达式技能。
掌握正则表达式将极大地提高你的文本处理效率,让你在 Ruby 编程中如虎添翼。祝你在正则表达式的学习之路上不断进步!