第16章 正则表达式类
从Regexp创建、anchor、字符类、重复与分组建立匹配语言,掌握options、capture、quote、sub/gsub、scan以及回溯资源边界。
学习目标
- 能解释正则表达式对象的创建方法与行首、行尾与字符范围在“第16章 正则表达式类”中的责任边界
- 能围绕“怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?”运行正常与故障轨迹并定位首个分岔
- 能用“第16章 正则表达式类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”
来源、版次与运行边界
“第16章 正则表达式类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第16章 正则表达式类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第16章 正则表达式类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?”,本页验收“第16章 正则表达式类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“用局部正则匹配代替结构化格式解析,错误接受残缺记录”;若无法定位第一处状态分岔,就拒绝当前解释。
从“找到了片段就验证成功”开始纠正
Regexp描述一门匹配语言;成功只证明某段input满足pattern覆盖的条件。/\d+/匹配"x12y",却不能证明整个input是整数。先于pattern技巧。
先预测:验证record应使用^...$还是\A...\z?Whole String validation通常用\A/\z;^/$按line boundary工作,multi-line input可能只验证其中一行。Anchor选择是security和schema决策。
正则表达式对象的创建方法
Literal /pattern/options在source中固定;Regexp.new(source, options)动态创建。Dynamic literal text必须Regexp.quote,dynamic grammar只允许受控片段。防止regex injection。
field = "user.name"
pattern = Regexp.new("\\A#{Regexp.quote(field)}=(.+)\\z")
p pattern.match("user.name=Ruby")Compile error抛RegexpError,应在config/load boundary提前发现;不要每行重新new同一pattern。Cache dynamic patterns时限制数量和length,避免unbounded memory。
行首、行尾与字符范围
\A/\z对应String起止,^/$对应line;[abc]匹配一个列举字符,[a-z]是range,[^...]否定class。要在examples中覆盖newline和non-ASCII。
POSIX classes如[[:alpha:]]比ASCII [A-Za-z]意图更宽,但Unicode/version语义需验证。Domain若只允许ASCII identifier,应明确ASCII,而不是把国际文本误收或误拒。
任意字符与反斜杠模式
.匹配一个字符,newline是否包含受option影响;它过于宽泛时会让delimiter grammar失守。Backslash sequences如\d、\s、\w、boundary与escaped metacharacters在Ruby source String和Regexp grammar之间可能要双重escape。
identifier = /\A[a-z_][a-z0-9_]*\z/i
integer = /\A[+-]?\d+\z/
p identifier.match("user_42")
p integer.match("-12")Literal regex减少String escaping;Regexp.new接收String时先由Ruby String parser处理。Debug时输出regexp.source和options,不只看source code视觉。
重复与最短匹配
*零次以上、+一次以上、?零或一次、{m,n}限定范围;默认quantifier通常greedy,后缀?改为lazy/minimal。不是简单“尽量多/少”。
html = "<b>one</b><b>two</b>"
p html.scan(/<b>.*<\/b>/)
p html.scan(/<b>.*?<\/b>/)即使lazy也不是HTML parser。Nested ambiguous repetition如(a+)+在失败长input上可能灾难回溯;限制input length、简化grammar、使用专业parser或runtime timeout机制(目标版本支持需验证),并benchmark失败路径。
分组与选择
Parentheses grouping改变quantifier/alternation作用范围并默认capture;(?:...)只group不capture。a|balternation优先级低,通常显式group。避免加括号后悄悄改变capture indices。
Named captures (?<name>...)比数字index稳健;optional group可返回nil。重复capture通常只保留某次结果,若需所有重复items用scan/二阶段parser。
正则表达式的选项
常见options包括忽略大小写、dot/newline、extended whitespace/comments等,具体flag按Ruby版本确认。不能为了一个token全局放宽grammar。
Extended mode便于复杂pattern分行注释,但literal space需escape;case-insensitive不等于locale-aware case folding。Inline option groups可限定局部,测试ASCII/Unicode和newline。
捕获与MatchData
match返回MatchData,含完整match、captures、named fields、pre/post match和offsets;失败nil。让Regexp只负责lexical evidence。
pattern = /\A(?<name>[a-z]+):(?<score>\d+)\z/i
match = pattern.match("Ruby:95")
raise ArgumentError, "invalid record" unless match
record = {
name: match[:name],
score: Integer(match[:score], 10),
}
p recordNumeric range、date validity等在capture后验证。Global match variables易被后续match覆盖,优先保留局部MatchData。Offsets单位与encoding需目标版本测试。
使用Regexp.quote的正则表达式
Regexp.quote/escape把dynamic text中的metacharacters转为literal pattern fragment。它不添加anchors、不限制长度,也不验证surrounding grammar。Search UI可quote query;admin-provided regex若允许grammar则必须权限、length、timeout和审计分开处理。
sub方法与gsub方法
Sub替首个,gsub替全部;replacement String解释backreferences,block返回replacement value更清楚。保护资源与数据。
source = "id=12 id=34"
masked = source.gsub(/\d+/) { |digits| "*" * digits.length }
p maskedBang variants修改receiver并可能在无变化时返回nil。Untrusted replacement用block避免backreference injection,限制match count/output bytes。Redaction pattern必须测试多line、encoding和旁路格式。
scan方法
Scan无capture时产出完整match Strings;有一组capture时常产出capture值,多组时产出Arrays。传block可逐项处理,避免materialize全部。容易因新增括号改变。
text = "A12 B7"
p text.scan(/\d+/)
p text.scan(/([A-Z])(\d+)/)
text.scan(/\d+/) { |digits| puts Integer(digits, 10) }只为group使用noncapturing parentheses,维持result shape。大量matches用block streaming,但Regexp engine仍需遍历input;取消/deadline由外层处理。
正式节点与章专属证据
- ↡在“第16章 正则表达式类”中,正则表达式对象的创建方法必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,行首、行尾与字符范围必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,任意字符与反斜杠模式必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,重复与最短匹配必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,分组与选择必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,Regexp.quote必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,正则表达式的选项必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,捕获必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,sub方法与gsub方法必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
- ↡在“第16章 正则表达式类”中,scan方法必须连接输入、状态变化与可复核结果。 :第 10 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?
输入与接收者
固定正则表达式对象的创建方法所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明行首、行尾与字符范围的允许状态。
观察证据
保存第16章 正则表达式类的初值、参数、编码或资源位置。
正式节点:正则表达式对象的创建方法、行首、行尾与字符范围、任意字符与反斜杠模式、重复与最短匹配、分组与选择、Regexp.quote、正则表达式的选项、捕获、sub方法与gsub方法、scan方法
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定正则表达式对象的创建方法的输入和接收者
- 02执行行首、行尾与字符范围并记录状态
- 03观察任意字符与反斜杠模式的返回或副作用
- 04用scan方法核对不变量并复位
运行不变量:模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。
边界故障探针
一次只破坏一个前提
本章回顾:Regexp提供文本证据,不完成全部验证
- Dynamic literal用Regexp.quote,trusted grammar与untrusted text分开;whole-input用正确anchors。
- Character class、wildcard、quantifier、group/alternation共同定义language,也决定回溯风险。
- Options局部可见,captures形成字段schema;保留MatchData而非依赖global match state。
- Sub/gsub明确首个/全部、replacement和output上限;scan result shape受captures影响。
- Match后仍需type/domain validation;输入长度、失败路径和runtime budget属于Regexp contract。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“用局部正则匹配代替结构化格式解析,错误接受残缺记录”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联正则表达式对象的创建方法、行首、行尾与字符范围、任意字符与反斜杠模式、重复与最短匹配、分组与选择、Regexp.quote、正则表达式的选项、捕获、sub方法与gsub方法、scan方法,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 正则表达式对象的创建方法
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 行首、行尾与字符范围
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 任意字符与反斜杠模式
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 重复与最短匹配
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 分组与选择
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- Regexp.quote
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 正则表达式的选项
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 捕获
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- sub方法与gsub方法
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- scan方法
“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。