POSTS
第十四章、ruby中的正则表达式
Ruby的正则表达式
在这部分和教程中我们将讨论正则表达式。
正则表达式用于文本搜索和更高级的文本操作。内建正则表达式的工具如grep、sed;文本编辑器如vi、emacs;编程语言如Tcl、Perl、Python。Ruby也内建支持正则表达式。
从另一方面来看正则表达式语法构成了一个文本匹配的领域专用语言。
pattern(模式)是一个正则表达式,它定义了我们要搜索或者操作的文本。它由文本字面和元字符构成。模式位于两个分隔符内。在Ruby中是//字符。它们通知正则表达式函数的开始和结束。
这里是元字符的部分列表。
. | 匹配任意一个字符 |
---|---|
* | 匹配前一个元素0次或多次 |
[] | 括号表达式。匹配括号内的一个字符 |
[^] | 匹配不在括号内的一个字符 |
^ | 匹配字符串的开始位置 |
$ | 匹配字符串的结束位置 |
| | 交替操作 |
=~操作符对字符串匹配正则表达式,如果匹配则返回匹配字符串的偏移量否则为nil。 Regexp类是用来开发正则表达式。还有两个速记的方式来创建正则表达式。下面的例子将显示它们。
#!/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}
第一个例子我们显示了对字符串应用正则表达式的三种方式。
re = Regexp.new 'Jane'
p "Jane is hot".match re
上面两行我们创建了一个简单的包含正则表达式文本的Regexp对象。使用match方法我们对”Jane is hot”句子应用这个正则表达式。检查’Jance’是否在这个句子中。
p "Jane is hot" =~ /Jane/
p "Jane is hot".match %r{Jane}
这两行完成相同的工作。两个斜杠//和%r{}字符是第一种方式的简写。在这个教程中,我们将使用斜杠。这是在许多语言中是事实上的标准。
$ ./regex.rb
#<MatchData "Jane">
0
#<MatchData "Jane">
这三种情况都匹配的。match方法返回匹配的数据,如果没有则返回nil。=~操作符返回第一个匹配的字符的位置,或者nil。
点字符
点字符是一个可以匹配任意单字符的正则表达式字符。注意必须要有些字符,它不能被忽略。
#!/usr/bin/ruby
p "Seven".match /.even/
p "even".match /.even/
p "eleven".match /.even/
p "proven".match /.even/
第一个例子,我们对字符串使用match方法应用正则表达式。match方法成功则返回匹配到的数据,否则返回nil。
p "Seven".match /.even/
“Seven”是一个字符串,可以调用match方法。这个方法的参数是一个模式。/.even/正则表达式模式是查找以任意字符开头接着是’even’的字符串。
$ ./dot.rb
#<MatchData "Seven">
nil
#<MatchData "leven">
nil
从输出结果中我们看到哪些是匹配的哪些是没有匹配的。
正如前面所说的,如果有点字符,那么就必须要有一个任意的字符。它不能被忽略。如果我们想到查找一个文本,其中有字符能被忽略的。换言之,我们想要一个模式可以同时匹配’seven’和’even’。对于这个我们可以使用一个?重复字符。?重复字符表示前一个字符可能出现0或者1次。
#!/usr/bin/ruby
p "seven".match /.even/
p "even".match /.even/
p "even".match /.?even/
这个脚本使用?重复字符。
p "even".match /.even/
这行打印nil,因为正则表达式在’even’之前要接受一个字符。
p "even".match /.?even/
我们稍微修改一下正则表达式。’.?‘代表没有字符或者有一个字符。这次匹配成功。
$ ./dot2.rb
#<MatchData "seven">
nil
#<MatchData "even">
输出结果。
正则表达式方法
前面的两个例子我们对正则表达式使用了match方法。除了match之外还有其他的方法也接受正则表达式参数。
#!/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/)
这个例子显示了正则表达式的一些方法。
puts "motherboard" =~ /board/
=~操作符将正则表达式放在右边,字符串放在左边。
puts "12, 911, 12, 111"[/\d{3}/]
正则表达式可以位于字符串后面的中括号内。这行打印第一个3个数字。
puts "motherboard".gsub /board/, "land"
使用gsub方法我们将’board’字符串替换成’land’。
p "meet big deep nil need".scan /.[e][e]./
scan方法查找字符串匹配。它会查找所有出现的匹配,而不仅是第一个。这行打印所有与模式匹配的字符串。
p "This is Sparta!".split(/\s/)
split方法使用给定的正则式来分隔字符串。\s字符代表了任何空白字符。
$ ./apply.rb
6
911
motherland
["meet", "deep", "need"]
["This", "is", "Sparta!"]
apply.rb脚本的输出结果。
特殊变量
一些使用正则式的方法会激活一些特殊的变量。包括上次匹配的字符串、上次匹配结果的前面部分和上次匹配结果的后面部分。这些变量方便了程序员。
#!/usr/bin/ruby
puts "Her name is Jane" =~ /name/
p $`
p $&
p $'
这个例子显示了三个特殊的变量。
puts "Her name is Jane" =~ /name/
这行代码是一个简单的正则式匹配。我们在’Her name is Jane’句子中查找’name’。我们使用=~操作符。这个操作符也会设置三个特殊的变量。这行返回数字4。
p $`
$`特殊变量包含了上次匹配结果之前的文本。
p $&
$&为匹配的文本。
p $'
$‘变量包含上次匹配结果之后的文本。
$ ./svars.rb
4
"Her "
"name"
" is Jane"
例子的输出结果。
锚点
锚点是在文本中匹配位置的字符。我们展示三个锚点字符。^字符匹配了行的开头;$字符匹配了行的结尾;\b字符匹配词的边界。
#!/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$/
第一个例子我们使用了^和$。
sen1 = "Everywhere I look I see Jane"
sen2 = "Jane is the best thing that happened to me"
有两个句子。’Jane’位于第一个的结尾第二个的开头。
p sen1.match /^Jane/
p sen2.match /^Jane/
这里我们查找这两个句子是否以’Jane’开头。
p sen1.match /Jane$/
p sen2.match /Jane$/
这里我们在句子结尾查找匹配。
$ ./anchors.rb
nil
#<MatchData "Jane">
#<MatchData "Jane">
nil
运行结果。
通常一个请求仅包含一个匹配全部的词。我们会默认计算所有匹配,包括更大的或者复合词。让我们通过一个例子来阐述。
#!/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’字符串。
text = "The cat also known as the domestic cat is a small,
usually furry, domesticated, carnivorous mammal."
问题是这个文本中有三个’cat’字符串。要匹配的’cat’表示一个哺乳动物,/cat/却匹配了’domesticated’词的8-10的字母。这不是我们想要的。
$ ./boudaries.rb
["cat", "cat", "cat"]
"The cat also known as the domestic cat is a small, \nusually furry, domesti"
"cat"
"ed, carnivorous mammal."
最后一次匹配的’domesticated’在下一个例子中将被使用\b锚点字符排除。
\b字符用于设置要查找的词的边界。
#!/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元字符进行了改善。
p text.scan /\bcat\b/
使用上面的正则式我们将查找’cat’整个词,不计算子词。
字符类
我们可以结合方括号将字符转换成字符类。字符类可以匹配在方括号内的任意字符。/[ab]/模式意味a或者b,相反的/ab/意味着a接着b。
#!/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
有一系列的6个3字母的单词。我们对数组的每个字符串采用特定字符集的正则式。
pattern = /[fs]it/
这个式模式在这个数组中查找到了’fit’、’sit’。我们从字符集使用’f’或者’s’。
$ ./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
有两个匹配的。
一个例子我们将进一步探讨字符类。
#!/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/
例子中有三个字符类正则式。
p "car".match %r{[abc][a][rs]}
这行的正则式由三个字符类构成。每个都对应一个字符。[abc]是a、b或者c。[a]就是a。第三个[rs]是r或者s。这里匹配’car’字符串。
p "car".match /[a-r]+/
我们在字符类中使用了一个连接符-。连接符是一个元字符表示一个范围:这里是a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, 或r。由于字符类仅对应一个字符。我们也可以使用+重复字符。这表示前一个字符集的字符可以重复一次或者多次。’car’字符串被匹配了。
p "23af 433a 4ga".scan /\b[a-f0-9]+\b/
这行有一个由三个子字符串构成的字符串。使用scan方法检查十六进制整数。我们有两个范围。第一个[a-f]代表a到f的字符。第二个[0-9]代表字数0到9。+表示这些字符可以重复多次。最后\b创建一个边界,表示仅接受由这些字符构成的字符串。
$ ./classes2.rb
#<MatchData "car">
#<MatchData "car">
["23af", "433a"]
输出结果。
如果字符类的第一个字符是脱字符(^),则对该类反转。它会匹配除了这些之外的任何字符。
#!/usr/bin/ruby
p "ABC".match /[^a-z]{3}/
p "abc".match /[^a-z]{3}/
这个例子我们在字符类中使用脱字符。
p "ABC".match /[^a-z]{3}/
我们查找一个有3个字母的字符串。这些字母不能在a到z之间。”ABC”字符串匹配这个正则式,因为所有的三个字符都是大写字母。
p "abc".match /[^a-z]{3}/
“abc”字符串没有匹配。
$ ./caret.rb
#<MatchData "ABC">
nil
输出结果。
量词
标记或者组后面的量词表示前面的元素允许出现多少次。
? - 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
上面是通常的量词列表。
#!/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/
例子中我们想要选择这些有3个字符的单词。\w字符是一个词字符。\w{3}意味着前面的词字符出现3次。
p "seven dig moon car lot fire".scan /\w{3}/
第一只是对每个字符串截取前三个字符。这不是我们想要的。
p "seven dig moon car lot fire".scan /\b\w{3}\b/
这是改进的搜索。我们将之前的模式放在边界符\b之间。现在仅查找有三个字符的单词。
$ ./nchars.rb
["sev", "dig", "moo", "car", "lot", "fir"]
["dig", "car", "lot"]
输出结果。
{n,m}是一个重复结构对于有n到m个字符的字符串。
#!/usr/bin/ruby
p "I dig moon lottery it fire".scan /\b\w{2,4}\b/
上面的例子我们选择有2、3、4个字符的单词。我们再次使用了边界符\b。
$ ./rchars.rb
["dig", "moon", "it", "fire"]
这个例子打印了一个有2-4个字符的单词数组。
下一个例子我们展示?元字符。接着?的字符是可选的。?之前的字符可以出现一次或者0次。
#!/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单词。这个单词有两种拼写方式,美式的’colour’和英式的’color’。我们想要两种都查找,此外我们还想查找复数形式。
p "color colour colors colours".scan /colou?rs/
colou?rs模式查找’colours’和’colors’。u字符在?字符之前表示是可选的。
p "color colour colors colours".scan /colou?rs?/
colou?rs?模式使u和s字符是可选的。因此我们会查找到这四个组合。
p "color colour colors colours".scan /\bcolor\b|\bcolors\b|\bcolour\b|\bcolours\b/
相同的请求可以间隔的写。
$ ./qmark.rb
["colors", "colours"]
["color", "colour", "colors", "colours"]
["color", "colour", "colors", "colours"]
输出结果。
这节的最后一个例子我们展示+字符。它允许前面的字符重复1次或多次。
#!/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
这个例子我们有一个数字数组。数字可以有一个或者多个数字字符。
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
我们遍历数组并对每个字符串应用正则式。表达式[0-9]+代表了0到9的任意字符重复0次或者多次。默认的这个表达式也会查找子字符串。 在34$34232中引擎认为34是一个数字。\b边界符在这里是无效的,因为我们没有具体的字符,引擎不知道该在哪里停止查找。这就是为什么我们在代码块中包含一个if条件式。仅当匹配结果等于原字符串才认为它是一个数字。
$ ./numbers.rb
234
1
23
53434
234532453464
23455636
34532452343452
343
2324
24221
这些的值是数字。
忽略大小写搜索
我们可以执行忽略大小写的搜索。正则表达式可以接一个选项。它是一个单一的字符,以某种方式修改模式。这里不区分大小写的搜索我们使用i选项。
#!/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
这个例子显示了区分大小写和不区分大小写的搜索。
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
这里我们使用i选项,接在第二个/字符后面。我们进行不区分大小写的搜索。这三行者匹配了。
$ ./icase.rb
#<MatchData "Jane">
nil
nil
#<MatchData "Jane">
#<MatchData "Jane">
#<MatchData "Jane">
例子的输出结果。
交替
一个例子解释交替操作符(|)。这个操作符可以创建一个有多个选择的正则式。
#!/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
names数组中有8个名字。我们将在数组中查找多虑字符串的组合。
pattern = /Jane|Beky|Robert/
这是搜索模式。它表示Jane, Beky和Robert是我的朋友。如果你查找他们就会找到我的朋友。
$ ./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
这是脚本的输出结果。
子模式
我们可以使用括号()创建子模式。
#!/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也会被匹配。
#!/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’。
$ ./subpatterns2.rb
0
0
0
nil
最后一个子模式没有匹配。记住0不意味着没有匹配。对于=~操作符,它是第一个匹配到的字符的索引。
邮箱例子
最后一个例子,我们创建一个正则式模式检查邮箱地址。
#!/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
注意这个例子提供了仅一种解决方案。它不需要是最好的。
emails = %w/ luke@gmail.com andy@yahoocom 23214sdj^as
f3444@gmail.com /
这是一个邮箱数组,仅有两个是有效的。
pattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z.]{2,5}$/
这是一个模式,第一个^和最后一个$是获取完整匹配。在模式之前和之后都不允许有字符。邮箱分为5部分。第一部分是本地部分,它通常是公司、个体或者昵称的名字。[a-zA-Z0-9._-]+列出了所有可能用于本地部分的字符,它们可以使用一次或者多次。第二部分是字面符@,第三部分是域名部分。它通常是邮箱的提供商,如 yahoo或者gmail。字符集[a-zA-Z0-9-]+指明了所有的可以用于域名的字符。+量词将这些字符使用一次或者多次。第四部分是点字符。它的前面接一个转义符。(.)因为点字符是一个元字符具有特殊意义。转义之后得到一个字面上的点。最后一部分是顶级域名。这个模式是[a-zA-Z.]{2,5}。顶级域名有2到5个字符,如sk, net, info, travel。这同样也有点字符,这是因此一些顶级域名有两部分如co.uk。
$ ./email.rb
[luke@gmail.com](mailto:luke@gmail.com) matches
andy@yahoocom does not match
23214sdj^as does not match
[f3444@gmail.com](mailto:f3444@gmail.com) matches
这个正则式标记了两个有效的邮箱地址。
在这章,我们学习了Ruby的正则表达式。zetcode原文地址