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 gsubsub

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 编程中如虎添翼。祝你在正则表达式的学习之路上不断进步!

THE END