基本正则表达式GNU扩展的平台支持
摘要
一篇探讨平台对基本正则表达式(BRE)GNU扩展支持的文章,具体是`\+`操作符,发现其在FreeBSD、macOS以及基于musl的发行版(如Chimera Linux)上均可正常工作。
<p><a href="https://lobste.rs/s/edml2s/platform_support_for_gnu_extensions">评论</a></p>
查看缓存全文
缓存时间: 2026/06/30 09:35
# 基本正则表达式中 GNU 扩展的平台支持
来源:https://www.wezm.net/v2/posts/2026/bre/
最近我审阅了一位同事编写的 shell 脚本:
``
if grep -e '@[^@]\+@' "$DIR/install.sh" ; then
``
当时我以为`\+`前面的`\`是个笔误,并指出如果要使用`\+`,可能需要传递`-E`以启用扩展正则表达式(ERE)支持。同事回复说,在基本正则表达式(BRE)中,`\\+`等同于 ERE 中的`\+`(表示一次或多次重复)。
这对我来说是个新知识!我想了解更多,于是查阅了 FreeBSD 的 `re_format(7)` 手册页(https://man.freebsd.org/cgi/man.cgi?query=re_format&apropos=0&sektion=7&manpath=FreeBSD+15.1-STABLE&format=html)。通常我对 BRE 和 ERE 区别的了解主要源于此,但其中并未提及这一点。我启动了一个 FreeBSD 虚拟机进行了简单测试,结果证实 `\\+` 确实可用。
与此同时,同事回复说,`\+` 并非 POSIX 规范中 BRE 的一部分,而是一个 GNU 扩展。引用最新的 POSIX 规范(https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html#tag_09_03_02)中的描述:
> ……`\?`、`\+` 和 `\|` 是匹配字面字符 `?`、`+` 或 `|`,还是按照 ERE 特殊字符 `?`、`+` 和 `\|` 的方式行为,由实现定义。
并附有说明:
> 本标准未来版本可能要求 `\?`、`\+` 和 `\|` 按照 ERE 特殊字符 `?`、`+` 和 `\|` 的方式行为。
因此,目前将 `\+` 视为 `+` 尚未标准化。
我好奇为什么 FreeBSD 的 `grep` 支持它,但 `re_format(7)` 却未提及,于是深入 FreeBSD 的源代码。这让我找到了 `regcomp.c`(https://github.com/freebsd/freebsd-src/blob/961f4814286820f242d8d5407b9fd7238e896936/lib/libc/regex/regcomp.c#L969):
``
#ifdef LIBREGEX
} else if (p->gnuext && EATTWO('\\', '?')) {
INSERT(OQUEST_, pos);
ASTERN(O_QUEST, pos);
} else if (p->gnuext && EATTWO('\\', '+')) {
INSERT(OPLUS_, pos);
ASTERN(O_PLUS, pos);
#endif
``
该功能于 2020 年 8 月引入(https://github.com/freebsd/freebsd-src/commit/18a1e2e9b9f109a78c5a9274e4cfb4777801b4fb)。`gnuext` 标志默认设置,除非在正则表达式上设置了 `REG_POSIX` 标志,而 `grep` 在基本模式下并未设置该标志(https://github.com/freebsd/freebsd-src/blob/9e1bbfb88e986b209709ea765189a3ebb6581bcd/usr.bin/grep/grep.c#L650)。
接下来我转向扩展的源头:glibc。glibc 中正则表达式语法非常可定制。基本正则表达式的定义(https://sourceware.org/git/?p=glibc.git;a=blob;f=posix/regex.h;h=2d8392cf84aeb42f9bbcd672f6e607d7be02f220;hb=bcbd6736005876dadd3d4751cad509568bc4bf28),`RE_SYNTAX_POSIX_BASIC` 如下:
``
# define RE_SYNTAX_POSIX_BASIC \
(_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM | RE_CONTEXT_INVALID_DUP)
``
而 `RE_BK_PLUS_QM` 是:
``
/* 如果此位未设置,则 + 和 ? 是运算符,而 \+ 和 \? 是字面量。
如果已设置,则 \+ 和 \? 是运算符,而 + 和 ? 是字面量。 */
# define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
``
深入探究此 GNU 扩展的起源,我发现它自 1995 年起就已存在于 glibc 中(https://sourceware.org/git/?p=glibc.git;a=blob;f=posix/regex.h;h=42afd848817959e62a160404815dc235f7941700;hb=2b83a2a4d978012cdf78b648337c31091e20526d)。我想知道该扩展的支持范围有多广。以下是我的发现:
- ✅ Chimera Linux(https://chimera-linux.org/)(及其他基于 musl 的发行版)—— 2016 年加入 musl libc(https://git.musl-libc.org/cgit/musl/commit/?id=25160f1c08235cf5b6a9617c5640380618a0f6ff)。
- ✅ macOS(https://en.wikipedia.org/wiki/MacOS)—— 似乎使用了大约 2009 年的 TRE 版本(https://github.com/laurikari/tre)。在 2021 年 10 月的代码转储(https://github.com/apple-oss-distributions/Libc/commit/0432a948ba54a0d62e6d3dd0b96a0f1e7dfd5fac)中似乎增加了对 `\+` 的支持(https://github.com/apple-oss-distributions/Libc/blob/71bbe350ab79eef58113991d817ccc6165061a64/regex/TRE/lib/tre-parse.c#L1635)。但对应代码在上游 TRE(https://github.com/laurikari/tre/blob/71bfcaf0af3994384987c6c2679ed7d078ffe189/lib/tre-parse.c#L1174)中似乎并不存在。
- ✅ NetBSD(https://netbsd.org/)—— 在 2021 年 2 月通过与 FreeBSD 同步(https://github.com/NetBSD/src/commit/1ee269c3a208a14da224b6e9917e2e9798961fff)支持此功能。
- ❌ OpenBSD(https://www.openbsd.org/)—— 似乎不支持(https://github.com/openbsd/src/blob/3a7ae229256d4c0f22c83dba5625ff455b6689a3/lib/libc/regex/regcomp.c#L465)。
- ❌ Illumos(https://illumos.org/)—— 似乎不支持(https://github.com/illumos/illumos-gate/blob/edd2f3461fcd719ff41d34b395ef3f5b5994fad1/usr/src/lib/libc/port/regex/regcomp.c#L700)。
- ✅ Redox OS(https://www.redox-os.org/)—— 使用 `posix-regex`(https://gitlab.redox-os.org/redox-os/posix-regex)crate,该 crate 确实实现了该扩展(https://gitlab.redox-os.org/redox-os/posix-regex/-/blob/063882aa6051b5075062d20d00b41ddc0fb8cd89/src/compile.rs#L214)。自 2018 年起(https://gitlab.redox-os.org/redox-os/posix-regex/-/commit/7648b78f45122826ca827ecd15111c4639aa68bd#line_16f7fc429_A254)。
- ✅ Haiku(https://www.haiku-os.org/)—— 支持(https://codeberg.org/haiku/haiku/src/commit/2b75ca9e1c15526b9ff7613363bb792a28a7f7ac/src/build/libgnuregex/regex.h#L179)。自 2014 年起通过导入 gnuregex(https://codeberg.org/haiku/haiku/commit/b55c918f579fb523946747cf26dde829fe7fe8c2)支持。
- ❌ SerenityOS(https://serenityos.org/)—— 不支持(https://github.com/SerenityOS/serenity/blob/341274075a32952223a8c8fe08e702fb7cbb2a04/Userland/Libraries/LibRegex/RegexParser.cpp#L399)。
### 结论
浏览众多开源操作系统的代码确实乐趣无穷。看到各种实现以及它们在可读性上的巨大差异很有意思。macOS 中的 TRE 是最难理解的,而 musl 一如既往地清晰。FreeBSD 虽然更复杂,但仍然相对直接。
最终结论是,这虽是一个非标准化扩展,但得到了较为广泛的支持,不过并非所有系统都支持。因此,当需要扩展功能时,最好使用 `-E` 或类似选项显式启用扩展正则表达式。
相似文章
可在‘各处’工作的正则表达式
本文讨论了正则表达式在sed、awk、grep和Emacs等工具之间移植的挑战,并提供了一组在这些环境中可靠工作的正则表达式子集。
GCC 16及以后版本中的BPF支持
何塞·马奇西(José Marchesi)和GCC-BPF团队提供了GCC 16中BPF支持的更新,突出了在与LLVM功能对等方面取得的进展,以及内核BPF自测通过率的提升。
关于C扩展、可移植性和替代编译器
本文讨论了编写可移植C代码的实际挑战,这些挑战源于对非标准编译器扩展和glibc条件头文件的依赖,并通过构建C编译器的示例进行说明。
@QuixiAI: With 编程语言取得了重大里程碑。PCRE2 已集成。正则表达式已整合。隐式 main 函数,单行命令已实现…
With 编程语言通过集成 PCRE2 支持正则表达式并实现隐式 main 函数,达到了一个重要里程碑。该更新支持单行命令,并包含具有内置 LSP 支持的自托管编译器。
字节码虚拟机在意外场景中的应用 (2024)
本文探讨了字节码虚拟机的出人意料的应用,特别是Linux内核中的eBPF以及编译后二进制文件中用于调试信息的DWARF表达式。