# Ruby 正则表达式

> 原文： [https://zetcode.com/lang/rubytutorial/regex/](https://zetcode.com/lang/rubytutorial/regex/)

在 Ruby 教程的这一部分中，我们将讨论 Ruby 中的正则表达式。

正则表达式用于文本搜索和更高级的文本操作。 正则表达式内置在`grep`，`sed`等工具中； 诸如 vi，emacs 之类的文本编辑器； Tcl，Perl，Python 等编程语言。 Ruby 也有对正则表达式的内置支持。

从另一个角度来看，正则表达式语法构成了用于匹配文本的特定于域的语言。

模式是一个正则表达式，用于定义我们正在搜索或操纵的文本。 它由文本文字和元字符组成。 该模式放置在两个定界符内。 在 Ruby 中，这些是`//`字符。 它们通知正则表达式函数模式的开始和结束位置。

以下是部分元字符列表：

| | |
| --- | --- |
| `.` | 匹配任何单个字符。 |
| `*` | 与前面的元素匹配零次或多次。 |
| `[ ]` | 括号表达。 与方括号内的字符匹配。 |
| `[^ ]` | 匹配方括号中未包含的单个字符。 |
| `^` | 匹配字符串中的起始位置。 |
| `$` | 匹配字符串中的结束位置。 |
| <code>&#124;</code> | 备用运算符。 |

`=~`运算符将正则表达式与字符串进行匹配，如果找到匹配项，则返回匹配项与字符串的偏移量，否则返回`nil`。 `Regexp`类用于开发正则表达式。 还有两种创建正则表达式的简便方法。 以下示例将显示它们。

```ruby
#!/usr/bin/ruby

re = Regexp.new 'Jane'
p "Jane is hot".match re

p "Jane is hot" =~ /Jane/
p "Jane is hot".match %r{Jane}

```

在第一个示例中，我们展示了在字符串上应用正则表达式的三种方式。

```ruby
re = Regexp.new 'Jane'
p "Jane is hot".match re

```

在以上两行中，我们创建一个`Regexp`对象，其中包含一个简单的正则表达式文本。 使用`match`方法，我们将此正则表达式对象应用于`"Jane is hot"`句子。 我们检查句子中是否包含`"Jane"`一词。

```ruby
p "Jane is hot" =~ /Jane/
p "Jane is hot".match %r{Jane}

```

这两行是相同的。 两个正斜杠//和% r {}字符是更冗长的第一种方式的简写。 在本教程中，我们将使用正斜杠。 这是许多语言的事实上的标准。

```ruby
$ ./regex.rb
#<MatchData "Jane">
0
#<MatchData "Jane">

```

在所有三种情况下，都有一个匹配项。 `match`方法返回匹配的数据，如果不匹配，则返回`nil`。 `=~`运算符返回匹配文本的第一个字符，否则返回`nil`。

## 点字符

点字符是正则表达式字符，可与任何单个字符匹配。 请注意，必须有一些字符。 它可能不会被省略。

```ruby
#!/usr/bin/ruby

p "Seven".match /.even/
p "even".match /.even/
p "eleven".match /.even/
p "proven".match /.even/

```

在第一个示例中，我们将使用`match`方法对字符串应用正则表达式。 `match`方法成功返回匹配的数据，否则返回`nil`。

```ruby
p "Seven".match /.even/

```

`"Seven"`是我们称为`match`方法的字符串。 该方法的参数是模式。 `/.even/`常规模式查找以任意字符开头，后跟`"even"`字符的文本。

```ruby
$ ./dot.rb
#<MatchData "Seven">
nil
#<MatchData "leven">
nil

```

从输出中，我们可以看到哪些字符串匹配，哪些不匹配。

就像我们上面说过的，如果有一个点字符，那么必须有一个任意字符。 不可省略。 如果我们要搜索可能会省略字符的文本怎么办？ 换句话说，我们想要一个既有`"Seven"`又有`"even"`的模式。 为此，我们可以使用`?`重复字符。 `?`重复字符表明前一个字符可能出现 0 次或 1 次。

```ruby
#!/usr/bin/ruby

p "seven".match /.even/
p "even".match /.even/
p "even".match /.?even/

```

该脚本使用`?`重复字符。

```ruby
p "even".match /.even/

```

该行打印`nil`，因为正则表达式期望在`"even"`字符串之前有一个字符。

```ruby
p "even".match /.?even/

```

在这里，我们对正则表达式进行了一些修改。 `.?`代表无字符或一个任意字符。 这次有一场比赛。

```ruby
$ ./dot2.rb
#<MatchData "seven">
nil
#<MatchData "even">

```

这是示例输出。

## Ruby 正则表达式方法

在前两个示例中，我们使用`match`方法处理正则表达式。 除了`match`以外，其他方法也接受正则表达式作为参数。

```ruby
#!/usr/bin/ruby

puts "motherboard" =~ /board/
puts "12, 911, 12, 111"[/\d{3}/]

puts "motherboard".gsub /board/, "land"

p "meet big deep nil need".scan /.[e][e]./
p "This is Sparta!".split(/\s/)

```

该示例显示了一些可用于正则表达式的方法。

```ruby
puts "motherboard" =~ /board/

```

`=~`是一个运算符，将右侧的正则表达式应用于左侧的字符串。

```ruby
puts "12, 911, 12, 111"[/\d{3}/]

```

可以在字符串后的方括号之间放置正则表达式。 该行将打印第一个包含三位数的字符串。

```ruby
puts "motherboard".gsub /board/, "land"

```

使用`gsub`方法，我们将'board'字符串替换为'land'字符串。

```ruby
p "meet big deep nil need".scan /.[e][e]./

```

`scan`方法在字符串中查找匹配项。 它查找所有事件，而不仅仅是第一次。 该行将打印所有与模式匹配的字符串。

```ruby
p "This is Sparta!".split(/\s/)

```

`split`方法使用给定的正则表达式作为分隔符来拆分字符串。 `\s`字符类型代表任何空白字符。

```ruby
$ ./apply.rb
6
911
motherland
["meet", "deep", "need"]
["This", "is", "Sparta!"]

```

我们看到`apply.rb`脚本的输出。

## Ruby 特殊变量

使用正则表达式的某些方法会激活一些特殊变量。 它们包含最后一个匹配的字符串，最后一个匹配之前的字符串和最后一个匹配之后的字符串。 这些变量使程序员的工作更加轻松。

```ruby
#!/usr/bin/ruby

puts "Her name is Jane" =~ /name/

p $`
p $&
p $'

```

该示例显示了三个特殊变量。

```ruby
puts "Her name is Jane" =~ /name/

```

在这一行中，我们有一个简单的正则表达式匹配。 我们在`"Her name is Jane"`句子中寻找一个“名字”字符串。 我们使用`=~`运算符。 该运算符还设置了三个特殊变量。 该行返回数字 4，这是比赛开始的位置。

```ruby
p $`

```

`$``特殊变量包含最后一个匹配项之前的文本。

```ruby
p $&

```

`$&`具有匹配的文本。

```ruby
p $'

```

`$'`变量包含最后一个匹配项之后的文本。

```ruby
$ ./svars.rb
4
"Her "
"name"
" is Jane"

```

这是示例的输出。

## 锚点

锚点匹配给定文本内字符的位置。 我们介绍了三个锚定字符。 `^`字符与行的开头匹配。 `$`字符与行尾匹配。 `\b`字符与单词边界匹配。

```ruby
#!/usr/bin/ruby

sen1 = "Everywhere I look I see Jane"
sen2 = "Jane is the best thing that happened to me"

p sen1.match /^Jane/ 
p sen2.match /^Jane/ 

p sen1.match /Jane$/ 
p sen2.match /Jane$/ 

```

在第一个示例中，我们使用`^`和`$`锚定字符。

```ruby
sen1 = "Everywhere I look I see Jane"
sen2 = "Jane is the best thing that happened to me"

```

我们有两个句子。 `"Jane"`一词位于第一个字母的开头和第二个字母的结尾。

```ruby
p sen1.match /^Jane/ 
p sen2.match /^Jane/ 

```

在这里，我们查看单词`"Jane"`是否位于两个句子的开头。

```ruby
p sen1.match /Jane$/ 
p sen2.match /Jane$/ 

```

在这里，我们在句子的末尾查找文本的匹配项。

```ruby
$ ./anchors.rb
nil
#<MatchData "Jane">
#<MatchData "Jane">
nil

```

这些就是结果。

常见的要求是仅包含整个单词的匹配项。 默认情况下，我们会计算所有匹配项，包括匹配较大或复合词的匹配项。 让我们看一个例子来澄清问题。

```ruby
#!/usr/bin/ruby

text = "The cat also known as the domestic cat is a small, 
usually furry, domesticated, carnivorous mammal."

p text.scan /cat/

p $`
p $&
p $'

```

我们有一句话。 在这句话中，我们寻找一只猫。 使用`scan`，我们查找句子中的所有`"cat"`字符串，而不仅仅是第一次出现。

```ruby
text = "The cat also known as the domestic cat is a small, 
usually furry, domesticated, carnivorous mammal."

```

问题在于文本内部有三个`"cat"`字符串。 除了匹配提到哺乳动物的`"cat"`以外，`/cat/`还匹配`"domesticated"`一词中的字母 8-10。 在这种情况下，这不是我们想要的。

```ruby
$ ./boudaries.rb
["cat", "cat", "cat"]
"The cat also known as the domestic cat is a small, \nusually furry, domesti"
"cat"
"ed, carnivorous mammal."

```

在下一个示例中，将使用`\b`定位符消除`"domesticated"`上的最后一次匹配。

`\b`字符用于为我们要查找的单词设置边界。

```ruby
#!/usr/bin/ruby

text = "The cat also known as the domestic cat is a small, 
usually furry, domesticated, carnivorous mammal."

p text.scan /\bcat\b/

p $`
p $&
p $'

```

通过包含`\b`元字符来改进示例。

```ruby
p text.scan /\bcat\b/

```

通过上面的正则表达式，我们将`"cat"`字符串作为整个单词来查找。 我们不计算子词。

```ruby
$ ./boudaries2.rb
["cat", "cat"]
"The cat also known as the domestic "
"cat"
" is a small, \nusually furry, domesticated, carnivorous mammal."

```

这次有两场比赛。 并且特殊变量可以正确显示最后一次匹配之前和之后的文本。

## 字符类

我们可以使用方括号将字符组合成字符类。 字符类与括号中指定的任何字符匹配。 `/[ab]/`模式表示`a`或`b`，而`/ab/`模式表示`a`后跟`b`。

```ruby
#!/usr/bin/ruby

words = %w/ sit MIT fit fat lot pad /

pattern = /[fs]it/

words.each do |word|
   if word.match pattern
       puts "#{word} matches the pattern" 
   else
       puts "#{word} does not match the pattern" 
   end
end

```

我们有六个六个三个字母的单词组成的数组。 我们对具有特定字符集的数组字符串应用正则表达式。

```ruby
pattern = /[fs]it/

```

这就是模式。 该模式在数组中查找`"fit"`和`"sit"`字符串。 我们使用字符集中的`"f"`或`"s"`。

```ruby
$ ./classes.rb
sit matches the pattern
MIT does not match the pattern
fit matches the pattern
fat does not match the pattern
lot does not match the pattern
pad does not match the pattern

```

有两场比赛。

在下一个示例中，我们将进一步探索字符类。

```ruby
#!/usr/bin/ruby

p "car".match %r{[abc][a][rs]}
p "car".match /[a-r]+/
p "23af 433a 4ga".scan /\b[a-f0-9]+\b/

```

该示例包含三个带有字符类的正则表达式。

```ruby
p "car".match %r{[abc][a][rs]}

```

此行中的正则表达式包含三个字符类。 每个都是一个字符。 `[abc]`是`a`，`b`或`c`。 `[a]`只是一个。 第三个`[rs]`是`r`或`s`。 与`"car"`字符串匹配。

```ruby
p "car".match /[a-r]+/

```

我们可以在字符类中使用连字符-字符。 连字符是表示以下字符的元字符：此处的`a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q`或`r`。 由于字符类仅适用于一个字符，因此我们也使用`+`重复字符。 这表示字符集中的前一个字符可以重复一次或多次。 `"car"`字符串满足这些条件。

```ruby
p "23af 433a 4ga".scan /\b[a-f0-9]+\b/

```

在这一行中，我们有一个包含三个子字符串的字符串。 使用`scan`方法，我们检查十六进制数。 我们有两个范围。 第一个`[a-f]`代表从 a 到 f 的字符。 第二个字符`[0-9]`代表数字 0 到 9。`+`指定这些字符可以重复多次。 最后，`\b`元字符创建边界，该边界仅接受仅包含这些字符的字符串。

```ruby
$ ./classes2.rb
#<MatchData "car">
#<MatchData "car">
["23af", "433a"]

```

This is the example output.

如果字符类的第一个字符是插入号`^`，则该类被反转。 它匹配除指定字符以外的任何字符。

```ruby
#!/usr/bin/ruby

p "ABC".match /[^a-z]{3}/
p "abc".match /[^a-z]{3}/

```

在示例中，我们在字符类中使用插入符号。

```ruby
p "ABC".match /[^a-z]{3}/

```

我们寻找一个包含 3 个字母的字符串。 这些字母可能不是`a`到`z`的字母。 因为所有三个字符均为大写字符，所以`"ABC"`字符串与正则表达式匹配。

```ruby
p "abc".match /[^a-z]{3}/

```

此`"abc"`字符串不匹配。 所有三个字符都在搜索范围之外。

```ruby
$ ./caret.rb
#<MatchData "ABC">
nil

```

这里有示例输出。

## 量词

标记或组后的量词指定允许该前一个元素出现的频率。

```ruby
 ?     - 0 or 1 match
 *     - 0 or more
 +     - 1 or more
 {n}   - exactly n
 {n,}  - n or more
 {,n}  - n or less (??)
 {n,m} - range n to m

```

上面是常见量词的列表。

```ruby
#!/usr/bin/ruby

p "seven dig moon car lot fire".scan /\w{3}/
p "seven dig moon car lot fire".scan /\b\w{3}\b/

```

在该示例中，我们要选择具有三个字符的单词。 `\w`字符是文字​​字符，`\w{3}`表示之前文字字符的三倍。

```ruby
p "seven dig moon car lot fire".scan /\w{3}/

```

第一行只是从每个字符串中剪切前三个字符。 这不是我们想要的。

```ruby
p "seven dig moon car lot fire".scan /\b\w{3}\b/

```

这是一个改进的搜索。 我们将先前的模式放在`\b`边界元字符之间。 现在，搜索将仅找到具有三个字符的单词。

```ruby
$ ./nchars.rb
["sev", "dig", "moo", "car", "lot", "fir"]
["dig", "car", "lot"]

```

示例的输出。

`{n,m}`是具有`n`到`m`个字符的字符串的重复结构。

```ruby
#!/usr/bin/ruby

p "I dig moon lottery it fire".scan /\b\w{2,4}\b/

```

在上面的示例中，我们选择了具有两个，三个或四个字符的单词。 我们再次使用边界`\b`元字符来选择整个单词。

```ruby
$ ./rchars.rb
["dig", "moon", "it", "fire"]

```

该示例打印具有 2-4 个字符的单词数组。

在下一个示例中，我们将介绍`?`元字符。 后面跟有`?`的字符是可选的。 形式上，`?`前面的字符可以出现一次或 0 次。

```ruby
#!/usr/bin/ruby

p "color colour colors colours".scan /colou?rs/
p "color colour colors colours".scan /colou?rs?/

p "color colour colors colours".scan /\bcolor\b|\bcolors\b|\bcolour\b|\bcolours\b/

```

假设我们有一个文本，我们要在其中查找颜色词。 这个词有两个不同的拼写，英语为`"colour"`和美国为`"color"`。 我们希望找到这两种情况，而且我们也希望找到它们的复数形式。

```ruby
p "color colour colors colours".scan /colou?rs/

```

颜色模式可以同时找到`"color"`和`"colour"`。 `?`元字符之前的`u`字符是可选的。

```ruby
p "color colour colors colours".scan /colou?rs?/

```

`colou?rs?`模式串使`u`和`s`字符成为可选字符。 因此，我们找到了所有四种颜色组合。

```ruby
p "color colour colors colours".scan /\bcolor\b|\bcolors\b|\bcolour\b|\bcolours\b/

```

可以使用轮换来写相同的请求。

```ruby
$ ./qmark.rb
["colors", "colours"]
["color", "colour", "colors", "colours"]
["color", "colour", "colors", "colours"]

```

This is the example output.

在本节的最后一个示例中，我们将显示`+`元字符。 它允许将前面的字符重复 1 次或更多次。

```ruby
#!/usr/bin/ruby

nums = %w/ 234 1 23 53434 234532453464 23455636
    324f 34532452343452 343 2324 24221 34$34232/

nums.each do |num|
    m = num.match /[0-9]+/

    if m.to_s.eql? num
        puts num
    end              
end

```

在示例中，我们有一个数字数组。 数字可以包含一个或多个数字字符。

```ruby
nums = %w/ 234 1 23 53434 234532453464 23455636
    324f 34532452343452 343 2324 24221 34$34232/

```

这是一个字符串数组。 其中两个不是数字，因为它们包含非数字字符。 必须将它们排除在外。

```ruby
nums.each do |num|
    m = num.match /[0-9]+/

    if m.to_s.eql? num
        puts num
    end              
end

```

我们遍历数组并将正则表达式应用于每个字符串。 表达式为`[0-9]+`，代表`0..9`中的任何字符，重复 0 次或多次。 默认情况下，正则表达式也会查找子字符串。 在`34$34232`中，引擎将 34 视为数字。 `\b`边界在这里不起作用，因为我们没有具体的字符并且引擎不知道在哪里停止寻找。 这就是为什么我们在块中包含`if`条件的原因。 仅当匹配项等于原始字符串时，该字符串才被视为数字。

```ruby
$ ./numbers.rb
234
1
23
53434
234532453464
23455636
34532452343452
343
2324
24221

```

这些值是数字。

## 不区分大小写的搜索

我们可以执行不区分大小写的搜索。 正则表达式后面可以有一个选项。 它是单个字符，以某种方式修改了模式串。 如果是不区分大小写的搜索，我们将应用`i`选项。

```ruby
#!/usr/bin/ruby

p "Jane".match /Jane/
p "Jane".match /jane/
p "Jane".match /JANE/

p "Jane".match /jane/i
p "Jane".match /Jane/i
p "Jane".match /JANE/i

```

该示例显示了区分大小写和不区分大小写的搜索。

```ruby
p "Jane".match /Jane/
p "Jane".match /jane/
p "Jane".match /JANE/

```

在这三行中，字符必须与模式完全匹配。 只有第一行给出了匹配项。

```ruby
p "Jane".match /jane/i
p "Jane".match /Jane/i
p "Jane".match /JANE/i

```

在这里，我们使用第二个`/`字符后面的`i`选项。 我们进行不区分大小写的搜索。 所有三行都匹配。

```ruby
$ ./icase.rb
#<MatchData "Jane">
nil
nil
#<MatchData "Jane">
#<MatchData "Jane">
#<MatchData "Jane">

```

This is the output of the example.

## 交替

下一个示例说明了交替运算符`|`。 该运算符使您可以创建具有多种选择的正则表达式。

```ruby
#!/usr/bin/ruby

names = %w/Jane Thomas Robert Lucy Beky
    John Peter Andy/

pattern = /Jane|Beky|Robert/ 

names.each do |name|    

    if name =~ pattern
        puts "#{name} is my friend"
    else
        puts "#{name} is not my friend"
    end
end

```

名称数组中有 8 个名称。 我们将在该数组中寻找字符串的多个组合。

```ruby
pattern = /Jane|Beky|Robert/ 

```

这是搜索模式。 它说，简，贝基和罗伯特是我的朋友。 如果找到其中任何一个，就找到了我的朋友。

```ruby
$ ./alternation.rb
Jane is my friend
Thomas is not my friend
Robert is my friend
Lucy is not my friend
Beky is my friend
John is not my friend
Peter is not my friend
Andy is not my friend

```

在这里，我们看到了脚本的输出。

## 子模式

我们可以使用括号`()`在样式内创建子模式。

```ruby
#!/usr/bin/ruby

p "bookworm" =~ /book(worm)?$/
p "book" =~ /book(worm)?$/
p "worm" =~ /book(worm)?$/
p "bookstore" =~ /book(worm)?$/

```

我们有以下正则表达式模式：`book(worm)?$`。 `(worm)`是子模式。 只有两个字符串可以匹配：`"book"`或`"bookworm"`。 `?`字符位于子模式之后，这意味着子模式在最终模式中可能会出现 0、1 次。 `$`字符在这里用于字符串的精确末尾匹配。 没有它，`bookstore`和`bookmania`这样的词也将匹配。

```ruby
#!/usr/bin/ruby

p "book" =~ /book(shelf|worm)?$/
p "bookshelf" =~ /book(shelf|worm)?$/
p "bookworm" =~ /book(shelf|worm)?$/
p "bookstore" =~ /book(shelf|worm)?$/

```

子模式经常与交替组合在一起以创建多个单词组合。 例如，`book(shelf|worm)`匹配`"bookshelf"`和`"bookworm"`，`book(shelf|worm)?`匹配`"bookshelf"`，`"bookworm"`和`"book"`。

```ruby
$ ./subpatterns2.rb
0
0
0
nil

```

最后一个子模式不匹配。 请记住，0 并不意味着没有匹配。 对于`=~`运算符，它是匹配字符串的第一个字符的索引。

## 电子邮件示例

在最后一个示例中，我们创建一个用于检查电子邮件地址的正则表达式模式。

```ruby
#!/usr/bin/ruby

emails = %w/ luke@gmail.com andy@yahoo.com 23214sdj^as
    f3444@gmail.com /

pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/

emails.each do |email| 

    if email.match pattern
        puts "#{email} matches"
    else
        puts "#{email} does not match"
    end

end

```

请注意，此示例仅提供一种解决方案。 它不一定是最好的。

```ruby
emails = %w/ luke@gmail.com andy@yahoocom 23214sdj^as
    f3444@gmail.com /

```

这是一系列电子邮件。 其中只有两个有效。

```ruby
pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/

```

这就是模式。 这里的第一个`^`和最后一个`$`字符可得到精确的模式匹配。 模式前后不允许有字符。 电子邮件分为五个部分。

第一部分是本地部分。 这通常是公司，个人或昵称的名称。 `[a-zA-Z0-9._-]+`列出了我们可以在本地部分使用的所有可能字符。 它们可以使用一次或多次。 第二部分是文字`@`字符。 第三部分是领域部分。 通常是电子邮件提供商的域名：例如 yahoo 或 gmail。 字符集`[a-zA-Z0-9-]+`指定可以在域名中使用的所有字符。 `+`量词使

使用一个或多个这些字符。 第四部分是点字符。 它前面带有转义字符`\`。 这是因为点字符是一个元字符，并且具有特殊含义。 通过转义，我们得到一个文字点。 最后一部分是顶级域。 模式如下：`[a-zA-Z.]{2,5}`顶级域可以包含 2 到 5 个字符，例如`sk, net, info, travel`。 还有一个点字符。 这是因为某些顶级域名（例如`co.uk`）有两个部分。

```ruby
$ ./email.rb
luke@gmail.com matches
andy@yahoocom does not match
23214sdj^as does not match
f3444@gmail.com matches

```

正则表达式将两个字符串标记为有效的电子邮件地址。

在本章中，我们介绍了 Ruby 中的正则表达式。