《精通正则表达式》学习笔记(二)


正则表达式的发展历程

  • 1968 年 Ken Thompson 的文章 Regular Expression Search Algorithm 描述了一种正则表达式编译器,该编译器生成了 IBM7094 的 object 代码。由此也诞生了他的 qed,这种编辑器后来成了 Unix 中 ed 编辑器的基础。

  • ed 有条命令 g/Regular Expression/p,读作 Gjobal Regular Expression,Print(应用正则表达式的全局输出)。这个功能最终成为独立的工具 grep,之后又产生了 egrep(Extended grep)。

    • POSIX(Portable Operating System Interface,可移植操作系统接口)诞生于 1986 年,它是一系列标准,确保操作系统之间的移植性。POSIX 把各种常见的流派分为两大类:
      Basic Regular Expressions(BREs)和 Extended Regular Expressions(EREs)。POSIX 程序必须支持其中的任意一种。

POSIX 正则表达式流派

  • Perl 的特性中值得一提的是,它提供了传统上只有专用工具 sedawk 才提供的正则表达式操作符——这在通用脚本语言中是个首创。正则引擎的代码来自一个早期的项目——Larry 的新闻阅读器 rn(其中的正则表达式代码来自 James Gosling 的 Emacs。(James Gosling 后来去开发他自己的语言 Java,Java 1.4 提供了一个标准的正则表达式包。)

  • 1997 年 Philip Hazel 开发了 PCRE,这是一套兼容 Perl 正则表达式的库,全面仿制 Perl 的正则表达式的语法和语义。其他的开发人员可以把 PCRE 整合到自己的工具和语言中,许多流行的软件都使用了 PCRE,例如 PHPApache 2EximPostfixNmap

正则表达式的注意事项和处理方法

若干常用工具的 Flavor 的简要考察

  • 一款工具软件能够利用正则表达式实现的功能,通常比它所属的正则流派更重要。

  • 程序设计语言有 3 种处理正则表达式的方式:

    1. 集成式(integrated):直接内建在语言之中,如 Perl。
    2. 程序式(procedural)
    3. 面向对象式(object-oriented)
    • 程序式和面向对象式,是由普通的函数接收普通的字符串,把它们作为正则表达式进行处理。由不同的函数进行不同的、关系到一个或多个正则表达式的操作。

字符串、字符编码和匹配模式

  • Unicode 是一组字符设定,或者是从数字和字符之间的逻辑映射的概念编码。一个 “代码点(code point)”,通常用十六进制来表示,以 “U+” 开头。

  • 支持 Unicode 的程序中的正则表达式通常支持 \unum 元序列,用来匹配一个具体的 Unicode 字符。

  • Unicode Version 3.1 诞生于 2001 年中期,增加了 u+FFFF 之后的代码点。例如,代表音乐谱号 C(Clef)的字符对应代码点 U+1D121。之前那些仅支持低于 U+FPPP 字符的程序无法处理这种情况。大多数程序的 \unum 只能支持最多 4 位十六进制数值。

  • 能够处理这类新字符的程序通常提供了 \x{nuwm} 序列,num 可以为任意多位数字(这是为了增强只支持 4 位数字的 \unum 表示法)。你可以使用 \x{1D121} 来匹配这类 “谱号 C” 之类的字符。

  • 正则模式和匹配模式

    • 不区分大小写的匹配模式:在匹配过程中会忽略字母的大小写。
    • 宽松排列和注释模式:忽略字符组外部的所有空白字符。字符组内部的空白字符仍然有效,# 符号和换行符之间的内容视为注释。
    • 点号通配模式(dot-match-all match mode,也叫 single-line mode “单行模式 “):点号不受限制,可以匹配任何字符,包括换行符。修改了点号处理换行符的方式,从 “需要特殊处理” 变为 “不需要特殊处理”
    • 增强的行锚点模式(Enhanced line-anchor match mode,也叫 “多行文本模式 “):「^」能够匹配字符串中内嵌的文本行的开头位置,「$」能够匹配字符串中内嵌的文本行的换行符。改变了「^」和「$」匹配换行符的方式,从 “不需要特殊处理” 变为 “需要特殊处理”。
    • 文字文本模式:几乎不识别任何正则表达式元字符。

常用的元字符和特性

  • 字符表示法

    • 字符缩略表示法:\n\t\a\b\e\f\r\v
    • 八进制转义:\num
    • 十六进制 /Unicode 转义:\xnum\x{num}\unum\Unum
    • 控制字符:\cchar
  • 字符组及相关结构

    • 普通字符组:[a-z][*a-z]
    • 几乎能匹配任何字符的元字符:点号
    • 单个字节:\C
    • Unicode 组合字符序列:\X
    • 字符组缩略表示法:\w\d\s\W\D\S
    • Unicode 属性、区块和分类:\p{Prop}\P{Prop}
    • 字符组运算符:[[a-z]&&[^aeiou]]
  • 锚点及其他 “零长度断言”

    • 行 / 字符串起点:^\A
    • 行 / 字符串终点:$\Z\z
    • 本次匹配的开始位置(或者上次匹配的结束位置):\G
    • 单词分界符:\b\B\<\>
    • 顺序环视 (?=…)(?!…)
    • 逆序环视 (?<=…)(?<!…)
  • 注释和模式修饰词

    • 模式修饰词:(?modifier),例如 (?i)(?-i)
    • 模式作用范围:(?modifier:…),例如 (?i:…)
    • 注释:(?#…)#…
    • 文字文本范围:\Q…\E
  • 分组,捕获,条件判断和控制:

    • 捕获 / 分组括号:(…)\1\2,…
    • 仅用于分组的括号:(?:…)
    • 命名捕获:(?<Name>…)
    • 固化分组:(?>…) 永远也不会 “交还” 分组内已经匹配的任何内容。
    • 多选结构:…|…|…
    • 条件判断:(?if then|else)
    • 匹配优先量词:*+?{num,num} 贪心(greedy)模式,默认的量词匹配模式,匹配尽可能多的内容。
    • 忽略优先量词:*?+???{num,num)? 懒惰模式,匹配尽可能少的内容,只需要满足下限,匹配就能成功。
    • 占有优先量词:*+++?+{nuwm,nuwm}+ 类似固化分组,一旦匹配某些内容,就不会“交还”。
  • 固化分组就是一个只进不出的栈,被匹配入栈的内容不会交还,这导致若匹配了更多无关内容,将导致匹配失败(因为之前的匹配无法交还),最终的匹配结果将为空。例如:!.+! 可以匹配 !Hola!,但 !(?>.+)! 无法匹配 !Hola!。其首先匹配尽可能多的内容 Hola!,但是之后的 ! 无法匹配,会强迫 .+ 释放之前匹配的 !,但固化分组无法释放,最终导致匹配失败。

  • 所有的正则引擎都不会对单词进行语意分析:它们认为 “NE14AD8” 是一个单词,而 “M.I.T.” 不是。