把自己关于 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)。
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
完整的描述是:零宽度,正向的先行断言 匹配以某种模式结尾的字符(但不包含匹配文本中的字符)
指定下一个出现的字符但不匹配它 零宽度意味着在字符串中,它不会消耗任何字符。表示 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. ---> '.' 虽然参与了先行断言,但是被没有被消耗,依旧可以被匹配
确保前面的字符匹配 reg,但不包含匹配 reg 文本中的字符
re = /(?<=David\s)BLACK/
re.match('David BLACK') # => #<MatchData "BLACK">
# Negative lookbehind assertion
reg = /(?<!David\s)BLACK/
reg.match('Jim BLACK') #=> #<MatchData "BLACK">
小技巧 我们可以把< 看成往字符左边走。从而区分正向和反向查找
(?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 形如:
# 分组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) 在先行断言中设置分组,不会消耗字符
以上难免有错误的地方,还请大牛们斧正。