第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 record

Numeric 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 masked

Bang 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由外层处理。

正式节点与章专属证据

  • :第 1 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 7 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 8 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 9 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。
  • :第 10 个正式节点要能回到“模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。”,并说明故障发生前后的第一处差异。

对象与状态模型

从输入、接收者到可观察证据

怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?

输入与接收者

固定正则表达式对象的创建方法所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明行首、行尾与字符范围的允许状态。

观察证据

保存第16章 正则表达式类的初值、参数、编码或资源位置。

正式节点:正则表达式对象的创建方法、行首、行尾与字符范围、任意字符与反斜杠模式、重复与最短匹配、分组与选择、Regexp.quote、正则表达式的选项、捕获、sub方法与gsub方法、scan方法

控制与消息轨迹

在相同初值下定位首个分岔

  1. 01固定正则表达式对象的创建方法的输入和接收者
  2. 02执行行首、行尾与字符范围并记录状态
  3. 03观察任意字符与反斜杠模式的返回或副作用
  4. 04用scan方法核对不变量并复位

运行不变量:模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。

边界故障探针

一次只破坏一个前提

基线满足:模式只处理声明的文本语法,匹配边界和捕获结果与原输入可对应。

本章回顾:Regexp提供文本证据,不完成全部验证

  1. Dynamic literal用Regexp.quote,trusted grammar与untrusted text分开;whole-input用正确anchors。
  2. Character class、wildcard、quantifier、group/alternation共同定义language,也决定回溯风险。
  3. Options局部可见,captures形成字段schema;保留MatchData而非依赖global match state。
  4. Sub/gsub明确首个/全部、replacement和output上限;scan result shape受captures影响。
  5. Match后仍需type/domain validation;输入长度、失败路径和runtime budget属于Regexp contract。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样把正则模式、输入编码、捕获范围和替换结果放进同一证据链?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“用局部正则匹配代替结构化格式解析,错误接受残缺记录”,应从哪里开始定位?
  1. 问题 3:覆盖正式节点。 用一个证据包串联正则表达式对象的创建方法、行首、行尾与字符范围、任意字符与反斜杠模式、重复与最短匹配、分组与选择、Regexp.quote、正则表达式的选项、捕获、sub方法与gsub方法、scan方法,说明为什么结论可由另一位读者独立复核。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

正则表达式对象的创建方法

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

行首、行尾与字符范围

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

任意字符与反斜杠模式

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

重复与最短匹配

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

分组与选择

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

Regexp.quote

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

正则表达式的选项

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

捕获

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

sub方法与gsub方法

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

scan方法

“第16章 正则表达式类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

本章以Ruby 基础教程第 5 版作者支持资料合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…