Ruby 学习 Ruby 正则的简单记录

ruomu · 2017年04月19日 · 最后由 ruomu 回复于 2017年04月23日 · 4170 次阅读

Ruby 正则知识记录

把自己关于 ruby 正则相关的知识做个小结。第一次发帖可能比较乱。

正则表达式的分组捕获

基于正则匹配的全局变量如 $~, $1,$2,$3... 是线程相关的本地全局变量。也就是说每个线程中的这些变量都是不一样的,$n在不同线程中不会冲突。

/(abc)/.match("abc")
t = Thread.new do 
  /(def)/.match("def")
  puts "$1 in the thread: #{$1}" #=> $1 in the thread: def
end.join

puts "$1 outside thread: #{$1}"  #=> $1 outside thread: abc

使用 () 圆括号指定捕获 (capture)。

具名捕获 (?<name>)

Ruby 中的正则可以允许我们给子表达式命名,以便后面使用。

re = /(?<first>\w+)\s+((?<middle>\w\.)\s+)?(?<last>\w+)/
m = re.match("David A. Black")  #=>  #<MatchData "David A. Black" first:"David" middle:"A." last:"Black">
m[:first]   #=> "David"

以上来自 The Well-Grounded Rubyist

正则表达式的断言 lookaround

1. 先行断言 (正向查找) LookAhead Assertions --- (?= reg)或(?!reg)

完整的描述是:零宽度,正向的先行断言 匹配以某种模式结尾的字符(但不包含匹配文本中的字符)

指定下一个出现的字符但不匹配它 零宽度意味着在字符串中,它不会消耗任何字符。表示 r 参与正向查找的字符串 依旧可以被匹配 正向的含义是确保 reg 匹配的字符 (串) 会出现。负向先行 (?!reg) 表示 reg 匹配的字符 (串) 不会出现

str = "123 456. 789"
m = /\d+(?=\.)/.match(str)
puts m[0]  # => 456

解释零宽度

str = "123 456. 789"
m = /\d+(?=\.)\./.match(str)
puts m[0]  # => 456.    ---> '.' 虽然参与了先行断言,但是被没有被消耗,依旧可以被匹配

2. 回顾断言(反向查找)LookBehind Assertions(?<= reg) 或(?<! reg)

确保前面的字符匹配 reg,但不包含匹配 reg 文本中的字符

  • (?<=re) Positive lookbehind assertion 正向回顾断言
  • (?<!reg) Negative loolbehind assertion 负向回顾断言
re = /(?<=David\s)BLACK/
re.match('David BLACK')  # => #<MatchData "BLACK">
# Negative lookbehind assertion
reg = /(?<!David\s)BLACK/
reg.match('Jim BLACK')  #=> #<MatchData "BLACK">

小技巧 我们可以把< 看成往字符左边走。从而区分正向和反向查找

3. 条件匹配--满足条件则匹配,不满足匹配其它的,类似三目运算符

(?if then|else) 在 Ruby 中的形式是/(?(A)X|Y)/ -- 满足条件 A 则匹配 X,否则匹配 Y

# if A is true, then match the expression X, else match Y
/(?(A)X|Y)/

# if A is false, the Y
/(?(A)|Y)/

两种最常用的的条件方式:A 形如:

  • A 为具名捕获 或者 数字检索捕获
# 分组1是否被捕获
/(?(1)foo|bar)/

# 名称为 "mygroup" 的是否被捕获
/(?(<mygroup>)foo|bar)/

example

如果一个号码以1开头,则必须要有一个三个数字的区域号
否则区域号是可选的(optional)
如下:800 为区域号
1-800-555-1212 # Valid
800-555-1212  # Valid
555-1212 # Valid

1-555-1212 # Invalid 缺少区域号

我们可以使用条件匹配来解决这个问题

re = /^(1-)?(?(1)\d{3}-|(\d{3})?)\d{3}-\d{4}/
"1-800-555-1212".match(re)
#=> #<MatchData "1-800-555-1212" 1:"1-" 2:nil>

"800-555-1212".match(re)
#=> #<MatchData "800-555-1212" 1:nil 2:"800-">

"555-1212".match(re)
#=> #<MatchData "555-1212" 1:nil 2:nil>

"1-555-1212".match(re)
#=> nil

Limitations 不足之处

使用 group-based conditionals(分组捕获作为 if 的条件匹配) 的不足在于,条件匹配不是零宽度字符匹配 (即会消耗字符) 。消耗的字符不能再被使用。

来个栗子

"100USD".match(/(USD)(?(1)\d+)/)
#=> nil

在 Perl 和其他语言里,我们可以在 if 的位置上添加 look-around statement. 但是 Ruby 语言不支持。但可以用些技巧来实现

在 look-around 中设置一个 group,此时的 group 不会消耗字符

"100USD".match(/(?=.*(USD))(?(1)\d+)/)
#=> #<MatchData "100" 1:"USD">

#  (?=.*(USD))  其中的 .*(USD)很重要。 先行断言 整个 "100USD", 即匹配的位置在0位置
#  (USD) 在先行断言中设置分组,不会消耗字符

以上难免有错误的地方,还请大牛们斧正。

最喜欢这种文章了... Google Driven Development(GDD) friendly

$1, $2等等的其实更像是局部变量……说是线程全局不同只是局部作用域不同的一种情况

考虑下面的代码是无法读到$1的,且每个局部作用域的$1啥的也不一样

  def process
     p $1 #不能正常工作
  end
#............
  str =~ regexp
  p $1 #普通情况,只要正确匹配了有分组1就行
  process
#...........

可以考虑传$~给 process

RGSS3 回复

谢谢指正。的确很像局部变量。而且是和线程挂钩的局部变量。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号