正则表达式

来自 Alpine Linux

本页总结了一些关于正则表达式的详细信息。它并非旨在作为任何正则表达式语言的教程,而是一个技术总结和“陷阱”(工具的不同实现可能以不同或意外方式运行的地方)列表。

警告: 此摘要是在我们使用 uClibc 时编写的。此处报告的 Alpine 实用程序的一些行为可能已随着切换到 musl 而发生变化。


Glob 模式

这些用于 shell 扩展和 case 表达式中的模式匹配。

  1. glob*
  2. gl?bbing
  3. [a-z][!-aeiou]

[!-aeiou] 在某些 shell(包括 BusyBox ash)中也可以表示为 [^-aeiou],但 [!...] 格式更具可移植性。

  • 如果模式包含无效的括号表达式或与任何现有文件名或路径名不匹配,则模式字符串将被字面解释。
  • 前导句点只能字面匹配,不能通过 [!a]?*[%-0][[:punct:]] 匹配。在 BusyBox ash 中,它与 [.a] 不匹配;其他 shell 实现可能有所不同。
  • / 只能字面匹配,并且解析优先级高于 [...],因此 a[b/c]d 仅匹配目录 a[b 中的文件 c]d
  • 给定模式 /foo/bar/x*/bam,目录 /foo 需要搜索权限,目录 bar 需要搜索和读取权限,每个 x* 目录都需要搜索权限。
  • 如果 set -f / set -o noglob,则禁用 glob 扩展。
    待办事项: 哪些上下文会自动抑制 glob 扩展?

问: 如何构造一个 shell glob 模式,以匹配除 "." 和 ".." 之外的所有文件?(来自 Unix FAQ 2.11)

模式 匹配 . 吗? 匹配 .. 吗? 匹配 .a 吗? 匹配 .ab 吗? 匹配 ..pdq 吗? 匹配 xyz 吗?
*
.*
.[!.]*
.??*

因此,要匹配最右边四列中的所有列,但不匹配最左边两列中的任何列,你需要组合三个 glob 模式:* .[!.]* .??*

如果你没有任何长度为 2 的文件名(如 .a),则可以使用更简单的模式:* .??*

POSIX 正则表达式

POSIX 定义了两类正则表达式语言:基本正则表达式 (BREs)扩展正则表达式 (EREs)。第一类在历史上由 grepseded 等实用程序实现。第二类由 egrep (grep -E)、awklexemacs 等工具实现。EREs 通常也可用作 sed 的一个选项;有时使用 sed -r 表示,有时使用 sed -E 表示。(BusyBox sed 使用前者。)

实际上,这些正则表达式语言的大多数实现都超出了规范,例如包括 Gnu 扩展(如 \w),或包括历史上仅可用(且仅为另一种正则表达式语言指定)的功能:因此,BREs 的大多数实现也将支持 \+\|,而 EREs 的许多实现也将支持反向引用(如 \1)。此外,awk EREs 遵守的规则与其他 EREs 略有不同。

注意: 从历史上看,使用 BRE 的正则表达式引擎是使用 NFAs 或“模式导向”正则表达式引擎实现的。EREs 通常使用更高效的 DFAs 或“文本导向”正则表达式引擎实现。这些之间存在理论上的转换,但在实践中,反向引用和非贪婪量化等设备只能通过 NFAs 有效地实现---因此,尽管 DFAs 更快,但它们也更受限制。

这些引擎的朴素实现之间的一个区别是 NFAs 更“渴望”。两种类型的引擎都将返回源文本中几个可能匹配项中最左边的匹配项;但 NFA 的更大渴望会体现在它用于匹配的几种替代模式中的哪一种。对于任何可以处理替代模式的正则表达式引擎,如果引擎是 DFA,则 printf "NFA, no I mean DFA" | regex_match "/NFA|NFA, no I mean DFA/" 将匹配整个源文本;但如果引擎是(朴素的)NFA,则只会匹配“NFA”,使用左侧模式。

然而,如今这种情况变得复杂,因为 POSIX 规定必须匹配源文本中最长的最左边延伸:因此,符合 POSIX 标准的 grep(它接受模式交替作为扩展)也必须匹配“NFA, no I mean DFA”,就像 egrep 一样。

此外,许多 ERE 引擎现在都支持反向引用,这也不能通过 DFA/“文本导向”技术有效地匹配,这也使情况变得复杂。


POSIX 为 BRE 和 ERE 规定

  • 匹配应该是文本中最长的最左匹配
  • 子模式贪婪匹配(匹配空字符串 "" 优于不匹配)
  • 空字符 (\0) 在文本和模式中都不允许

在下面,我将 BusyBox 工具(grepegrep、带有和不带有 -rsed 以及 awk)与 Gnu 核心工具以及 FreeBSD 9 基础系统中的这些工具的版本进行了比较。我将 Gnu 的 awk 实现称为 “gawk”(Gnu 本身也是如此);我将 FreeBSD 的 awk 实现称为 “nawk”(尽管严格来说,这只是 nawk 的几种实现之一)。


BREs

此材料正在编写中...


(最后编辑者:Sertonix,于 2023 年 9 月 20 日。)

特殊字符:. * [] ^ $ \(\) \{\} 点号和 [^x] 应该匹配换行符;但在任何 grep -z 实现中,它们都不这样做。锚点 ^ $ POSIX 规定,当不在模式/组/交替的开头时,按字面意义解释(FreeBSD grep 在 ^ 方面存在错误)所有 [e]greps 都匹配行首和行尾,即使使用多行缓冲区也是如此(尽管 FreeBSD 的 [e]grep -o ^... 存在错误)另一方面,Sed 引擎仅将这些锚点与缓冲区开头和缓冲区结尾进行匹配。与 \| 的交替 不在 BRE 的 POSIX 中,但被许多 <code>grep</code> 和 <code>sed</code> 接受 FreeBSD 工具在以下上下文中不处理 \|:“_ ... | _ ... ( _ ... _ ) ... _” 区间 \{m,n\} \? 用于 \{0,1\},\+ 用于 \{1,\},这些不在 POSIX 中,但通常被接受。除 FreeBSD 的所有 greps 和 seds 都支持 \| \? \+ 和其他 Gnu 扩展 未匹配的 \{ 被拒绝为错误 未匹配的 \} 通常被视为字面量;只有不带 -r 的 FreeBSD sed 会拒绝 Gnu sed 接受 \{,1\};其他拒绝,所有都拒绝 \{\}, \{2,1\} 和 \{1,2,3\} 位置不当的量词 在模式/组/交替的开头(如果在 ^ 之后)被视为字面量 这对于 *, \?, 和 \+ 在它们可用时是正确的 但 \{1\} 反而会被 FreeBSD 和 Gnu sed 拒绝为错误 FreeBSD 和 Gnu grep,BusyBox grep 和 sed:接受相邻的量词而不会出错 Gnu 和 FreeBSD sed:当 \{...\} 和 * 跟随另一个量词时拒绝它们 Gnu sed:允许 \? 和 \+ 这样做;FreeBSD 不将其识别为量词 组 \( \) 和反向引用 \1-\9 ^\(ab*\)*\1$ 匹配 ababbabb 但不匹配 ababbab(反向引用到先行模式的最后一次匹配) 未匹配的 \( 或 \) 被拒绝为错误 <var>c</var> POSIX 没有定义其对于普通字符 <var>c</var> 的行为。我检查的所有实现都将其解释为匹配字面量 <var>c</var>。括号表达式 [...] 在括号内,^ 是字面量,除非在开头,.*[\ 都是字面量 以下所有内容都是 POSIX 要求的(''斜体字除外''):] 在 []...] 和 [^]...] 中被视为字面量 - 在 [\-...]、[^\-...] 和 [...\-] 中被视为字面量,因此 [b\-a] 是 b,\,],^,_,`,a 的集合;而不是 b,-,a 的集合 \ 在 [...\\...] 中被视为字面量,因此 [a\]b] 是 [a\],然后是字面量 b];而不是 a,\\],b 的集合 [:<var>classes</var>:] [:alpha:] [:alnum:] [:upper:] [:lower:] [:xdigit:] [:digit:] 但不是 \d [:punct:] [:graph:] 可见字符,空格除外 [:blank:] 空格或制表符 [:space:] 空格或制表符或 \n \r \v \f [:print:] 可见字符加上空格 [:cntrl:] ASCII < 32 或 127 未匹配的 [ ''被拒绝为错误'' 未匹配的 ] 被视为字面量 [m-ax] ''BusyBox [e]grep 和 nawk 视为仅 x,其他拒绝'' [a-m-xy] ''BusyBox [e]grep 和 nawk 视为 [a-my],其他拒绝''

EREs

此材料正在编写中...


(最后编辑者:Sertonix,于 2023 年 9 月 20 日。)

. * ? + [] ^ $ | () {} POSIX 要求 “long|longest” 应匹配 “longest” 的所有内容,因此实现必须至少以这种方式模拟 DFA 点号和 [^x] 应该匹配换行符;但在任何 egrep -z 实现中,它们都不这样做 ^ $ 始终是锚点 所有 [e]greps 都匹配行首和行尾,即使使用多行缓冲区也是如此(尽管 FreeBSD 的 [e]grep -o ^... 存在错误) sed 和 awk 引擎仅将这些锚点与缓冲区开头和缓冲区结尾进行匹配,因此 \".^b\" 应该在 grep 中匹配 $'a\nb' 的最后两个字符(如果只有点号匹配换行符),但在 sed 中则不匹配;这就是我们观察到的情况,除了 Gnu sed 和 gawk 只会将 '^pat' 与缓冲区开头匹配,但会将 '.^pat' 与行首匹配('pat$.' 也是如此);此外,nawk 拒绝 '.^b' 但不拒绝 'a$.' | FreeBSD sed 和 nawk 在以下上下文中拒绝 |:“_ ... | _ ... ( _ ... _ ) ... _”;FreeBSD egrep 静默地无法匹配 \{m,n\} nawk 和 gawk --traditional 将其视为字面量 BusyBox 和 gawk --re-interval 处理(这是某些 gawk 版本的默认设置) 未匹配的 \{: BusyBox 工具、Gnu sed 和 gawk 拒绝为错误;FreeBSD 工具和 Gnu egrep 将其视为字面量 未匹配的 \}: 被视为字面量(只有不带 -r 的 FreeBSD sed 会拒绝) Gnu sed 和 gawk 接受 \{,1\};其他拒绝,所有都拒绝 \{\}, \{2,1\} 和 \{1,2,3\};除非 FreeBSD egrep 奇怪地处理第三个,而 FreeBSD sed 将第一个视为字面量 模式/组/交替开头(如果在 ^ 之后)的量词:egreps 静默地删除(BusyBox 删除整个构造,其他仅删除第一个字符);带有 -r 的 seds 和 awks 通常会拒绝(尽管 gawk 将此类量词和任何前面的 ^ 视为字面量;nawk 接受 ^* ^+ ^? 但很难说它们匹配什么) BusyBox 和 Gnu egrep 和 sed 和 awk,FreeBSD egrep 和 nawk:接受相邻的量词而不会出错;FreeBSD sed 拒绝所有内容 ( ) 我所有的 egrep 实现都提供反向引用(因此对于这些模式,不运行 DFA) ^(ab*)*\1$ 匹配 ababbabb 但不匹配 ababbab(反向引用到先行模式的最后一次匹配) 带有 -r 的 FreeBSD sed:\1 是字面量 “1” 而不是反向引用;其他 seds 提供反向引用 awks:\1 是 \x01 而不是反向引用 未匹配的 (: 被拒绝为错误 未匹配的 ): 被拒绝为错误,除非带有 -r 的 FreeBSD 和 BusyBox sed,以及 BusyBox awk 和 gawk,将其视为字面量 \ordinary 未定义(我所有的 egrep 和 sed 实现都将其视为字面量;awks 接受 C 转义符,但在其他情况下会将其视为字面量,可能会发出警告) 在括号内,^ 是字面量,除非在开头,.*[\?+|$ 都是字面量 否则,与 BREs 相同 在 gawk 和 nawk 中,[a\]1] 匹配 a,\],1。在 BusyBox awk 和 egrep/sed 中,它匹配 a,\,后跟字面量 1,然后是 ]。

Gnu 扩展

这些存在于我测试的所有 [e]greps 中,以及除 nawk(和 gawk --traditional)之外的所有 seds 和 awks 中。它们仅在括号表达式之外被特殊处理,即使在 awks 中也是如此,后者仍然在那里特殊处理 \t 等。

  1. \w\W 用于 [[:alnum:]_][^[:alnum:]_]
  2. \s\S 用于 [[:space:]][^[:space:]],匹配以下任何一项:空格 制表符 \n \r \v \f
    (BusyBox 工具和某些版本的 gawk 缺少。)
  3. \b \B \<\>,在单词边界处的零宽度匹配(\B单词边界)
    (在 awks 中,\b 而是指 “\x08”;\ygawk 中替代 \b,BusyBox awk 中没有替代 \b 的。)
    FreeBSD 的 [e]grep -o \b...[e]grep -o \<... 目前存在错误;BusyBox 的 sed 和 awk 在单词开头使用 \< \b \B 时存在错误。
  4. \` \' 缓冲区开头和缓冲区结尾锚点(此处未调查的某些正则表达式引擎使用 \A \Z \z 代替)
    (在 awks 中,^$ 已经具有此行为,即使针对包含换行符的源文本也是如此。)
    FreeBSD 的 [e]grep 目前错误地将这些与行首和行尾而不是缓冲区开头和缓冲区结尾匹配。此外,FreeBSD 的 grep -o '\`...' 在某些方面存在错误,而 grep -o '...'\' 则没有。BusyBox sed 和 awk 在使用 \` 时也存在错误,而使用 \' 时则没有。所有这些错误都已报告。

C 转义符

\n \t \r \x09 \f \v \a \c
这些由 awks 特殊处理(尽管 nawk 仅支持到 \f),即使在括号内也是如此。
它们也由 Gnu 的 sed 特殊处理。BusyBox 的 sed 支持 \n \t \r;FreeBSD 的 sed 支持 \n。其他转义符不受这些 seds 支持,我测试的任何 grep 都不支持。

这些转义符也可能在你的 shell 的 $'...' 构造中被特殊处理;\OOO(最多 3 位八进制数字)、\uXXXX(4 位十六进制数字)、\e \E \b \' 也可能被特殊处理。Awk 引擎也处理了这些后来的形式中的一些。如上文所述,最后两个被某些正则表达式引擎以不同的方式处理。

注意

  • 所有这些正则表达式引擎都将 \d 视为字面量 “d”,而不是 [[:digit:]]
  • BusyBox sed 将仅匹配一次 ""(空字符串);如果 /g 修饰符打开,则其他将匹配多次。
  • Grep 引擎会将模式中的换行符视为等同于 \|;sed 和 awk 引擎将拒绝并报错。
  • FreeBSD 的 sed 将强制存在终端 \n,即使输入中不存在也是如此。一些其他 FreeBSD 工具(如 cut)也会这样做;另一些(如 tr)则不会。在 Gnu 的 sed 中,命令 q 也强制终端 \n
  • BusyBox 的 grep -oz 在每个结果后添加 “\0”(空字符)后缀;其他 greps 添加 \n 后缀。
  • 非贪婪量词 pat?? pat*? pat+? pat\{m,n\}? 在 POSIX 规范中未提供,此处讨论的任何符合 POSIX 标准的工具也未提供。
  • FreeBSD grep 和 egrep 在位置 0 匹配空字符串:printf 'cba' | egrep -o '[ba]*'。我检查的其他任何 [e]grep 实现(例如 BusyBox 或 Gnu 的)都不会这样做。

Lua 中的正则表达式

字符串转义

在 Lua 中,正则表达式模式始终以字符串形式提供,因此将支持字符串上的所有常规转义符

\\
\"
\'
\a for bell, \x07
\b for backspace, \x08
\t for \x09
\n for \x0a
\v for \x0b
\f for \x0c
\r for \x0d

Lua 字符串也接受 ddd 用于十进制数字 d。(注意:不是八进制数字。)从 Lua 5.2 开始,也接受用于十六进制数字的 \xhh

字符串可以写在匹配的单引号或双引号内。它们也可以写在

[[constructs
like this]]

[=[
or [[like]]
\this]=]

在这些构造中,转义序列(如 \t)不会被扩展。它和嵌入的 [[like]] 都被字面解释。此外,当字符串的第一个字符是换行符时,它会被忽略。

正则表达式引擎

基本的 Lua 正则表达式引擎比 Posix 或 PCRE 风格的语言更有限,但仍然非常强大。事实上,与更熟悉的引擎相比,Lua 引擎更容易完成某些事情。如果基本的 Lua 引擎对于你的目的而言仍然太有限,你应该考虑 LPEG 或 Lrexlib 库。前者在 Lua 社区中更强大且被广泛使用;后者与更熟悉的正则表达式引擎库和语言接口。

正则表达式特殊字符

以下序列对于基本的 Lua 正则表达式引擎具有特殊含义。

字符类
. 匹配任何字符
%z 在 Lua 5.1 中,正则表达式引擎不会读取模式字符串中嵌入的 \0 之后的内容,因此提供了此特殊序列来匹配源文本中的 \0(并允许模式字符串继续)。在 Lua 5.2 中,现在可以直接使用嵌入的 \0。使用默认编译设置,%z 仍然被支持,但已弃用。
%a%A 类似于 POSIX [[:alpha:]] 和 [^[:alpha:]] %l%L 类似于 POSIX [[:lower:]] 和 [^[:lower:]] %u%U 类似于 POSIX [[:upper:]] 和 [^[:upper:]]
%w%W 类似于 POSIX [[:alnum:]] 和 [^[:alnum:]]。(请注意, Gnu 正则表达式扩展 \w不同,Lua 中的模式 %w 和 POSIX 中的 [[:alnum:]]匹配下划线。)%d%D 类似于 POSIX [[:digit:]] 和 [^[:digit:]] %x%X 类似于 POSIX [[:xdigit:]] 和 [^[:xdigit:]]
%s%S 类似于 POSIX [[:space:]] 和 [^[:space:]]。(%s 和 POSIX [[:space:]] 也匹配垂直空格(\n \r \f \v,而 POSIX [[:blank:]] 仅匹配 \x20 和制表符。)%p%P 类似于 POSIX [[:punct:]] 和 [^[:punct:]],排除空格、字母数字和控制字符 %c%C 类似于 POSIX [[:cntrl:]] 和 [^[:cntrl:]] %g%G 类似于 POSIX [[:graph:]] 和 [^[:graph:]],所有可见字符(空格除外);仅在 Lua 5.2 中如此解释 POSIX [[:print:]],所有可见字符加上空格,不可直接使用。使用 [%p%w ][%g ]
括号表达式
[class][^class] 可以包含以下序列
  • 单个字符,如 a\t
  • 范围,如 a-m
  • 字符类特殊字符,如 %a
组和反向引用
  • (pat) 将匹配 pat 的源文本捕获到一个组中。与其他正则表达式引擎不同,这些构造不能后跟量词(如 *+
  • %1 反向引用到捕获的组(对比 \1,它是并匹配 “\x01”,以及 \\1,它匹配字面字符 “\” 然后是 “1”)
  • () 捕获匹配源中的当前位置到一个组中,而不是文本
量词
  • ?*+ 是熟悉的贪婪量词
  • -* 的非贪婪变体
请注意,在 Lua 中,量词只能跟随
  • 单个字符
  • 正则表达式特殊字符,如 .%a
  • [class] 表达式
它们不能跟随任意 (pat)
锚点 ^ $
与 POSIX BREs 中一样,当不在锚定位置时(如在模式 ab^cd 中),这些被视为字面字符
字面字符
%c 这是一个字面量 c,对于任意字符 c。它取消了字符 ( ) . % + - * ? [ ] ^ $ 的特殊含义


交替(在其他正则表达式语言中使用 | 表示)在 Lua 中不可用;它只能使用 [class] 构造来近似。

Lua 拥有而其他引擎缺乏的两个巧妙的原语是

  • %b() 平衡的 (...) 内部(包括)文本;可以使用其他字符代替 ()
  • %f[class] (源的开头或) 不匹配 class 的文本与 (源的结尾或) 匹配 class 的文本之间的零宽度“边界”。这是 Gnu 正则表达式特殊字符 \<\> 的概括。示例
    %f[%x] matches the source text "123-567 9ab" before positions 1, 5, and 9
    %f[%X] matches the same source text before positions 4, 8, and 12.