Regex 并没有那么难

2023年7月11日 星期二 Regex Isn't Hard

Regex 因为过于复杂而声名狼藉。这很正常,但我也认为,如果你专注于 Regex 的某个核心子集,它并没有那么难。 大部分复杂性来自于各种难以记忆的“快捷方式”。 如果你忽略这些,这种语言本身就相当小巧,并且可以在各种编程语言中移植。

学习 Regex 是值得的,因为你可以用非常少的代码完成 很多 工作。 如果我尝试使用常规的过程式代码来复制我的 Regex 所做的事情,通常会非常冗长、充满缺陷且速度明显较慢。 往往需要花费数小时甚至数天才能做得比编写几分钟的 Regex 更好。

注意: 像 Rust 这样的某些语言具有 parser combinators,它们在大多数我关心的方面都可以和 Regex 一样好,甚至更好。 然而,我仍然经常选择 Regex,因为它需要我记住的东西更少。 所有主流编程语言都支持一个 Regex 的核心子集。

你需要了解四个主要概念:

  1. Character sets(字符集)
  2. Repetition(重复)
  3. Groups(分组)
  4. |^$ operators(操作符)

在这里,我将重点介绍 Regex 语言的一个子集,它并不难理解或记住。 在整个过程中,我还会告诉你应该忽略什么。 这些东西大多数是快捷方式,它们以大量的复杂性为代价来节省一些冗长。 我宁愿冗长也不要复杂,所以我坚持使用这个子集。

Character Sets(字符集)

Character set 是 Regex 中可用的最小文本匹配单元。 它只是一个字符。

Single characters(单个字符)

a 匹配一个字符,总是小写 aaaa 是 3 个连续的 character sets,每个只匹配 aabc 也是如此,但第二个和第三个分别匹配 bc

Ranges(范围)

匹配一组字符中的一个。

请注意,在最后一点中,- 是如何放在最后的。 还要注意,^ 不是范围内的第一个字符,如果 ^ 作为 character set 或 Regex 中的第一个字符出现,则 ^ 可以成为一个 operator。

这里与布尔逻辑有一个相似之处:

你可以使用 groups 和 negation 构建更复杂的逻辑。

Negation (^)(否定)

我稍后会提到这个 operator,但在 character sets 的上下文中,它表示“除了这些之外的所有内容”。

例子:

[Ignore this stuff](忽略这些东西)

这些东西是不必要的复杂。它们以大量的复杂性为代价来节省一些冗长。

Repetition(重复)

这些 operators 更改紧接在前的 character set 以匹配特定次数:

所有这些也适用于整个 groups。

[Ignore this stuff](忽略这些东西)

这些是不必要的复杂。你可以通过其他方式完成相同的事情。

Groups(分组)

Group 基本上是一个子 Regex。 Groups 有三个常见的用途:

1. Repeat a sub-pattern(重复一个子模式)

例如,此 pattern ([0-9][0-9]?[0-9]][.])+ 匹配一个、两个或三个数字,后跟一个 .,并且还匹配此 pattern 的重复模式。 这将匹配一个 IP address(即使不完全正确)。

2. Substitutions(替换)

最常见的 Regex 操作是 match 和 substitute。 但是,subtitution 的 API 因 host langauge 而异。

在这两种情况下,你都可以使用 $1\1。 在文档中查找哪个适合。

3. Extract text(提取文本)

你可以提取 group 匹配的文本。

不可移植的部分是,访问 groups 的 API 在每种编程语言中几乎总是不同的。 尽管如此,group extraction 非常有用,所以只需查找一下。

最常见的 APIs 如下所示:

如果这些 APIs 不存在,或者如果你不想记住它,你可以通过 subtitution 复制 extraction。 例如,在 Python 中,你可以执行 re.sub("([^\n]*\\.foo)[^\n]*", "$1", input_str) 来提取第一个 group。

[Ignore this stuff](忽略这些东西)

在 groups 的开头有一些 operators,例如 (?:,它们可以表示各种东西,例如“non-capturing group”或“look-ahead”或“look-behind”。 这些是相当高级的,通常你可以不用了解它们。

The |^$ Operators(操作符)

| operator 是 OR,但适用于整个 Regex 或 groups。

^ 只有当它是第一个字符时才重要:

$ 字符仅表示“结束”,并且仅在 top-level Regex 中使用。

Conclusion(结论)

始终只坚持使用 Regex 的这个子集并不是一个坏主意,因为它主要可以在各种编程语言中移植。 这意味着需要记住的东西更少,因此你在将信息塞入你的大脑方面获得了很大的“性价比”。 确实存在的怪癖相对较少,并且通常由于它们提供的价值而值得付出努力。

关于可移植性 — 大多数现代实现都试图复制 Perl Regex 的某个子集。 我在此处概述的子集在当今的主要编程语言中非常一致。 但是,如果你使用的是 sedgrep 之类的旧工具,它们是大约在 Perl 开发 Regex 概念的同时创建的,你可能会遇到一些意外。 但是,较新的实现相当稳定。

太多时候人们完全拒绝 Regex,这很可惜,因为它是一种非常强大的文本处理语言。 稍微了解一下 Regex 知识会很有帮助。 我希望这有帮助!