1. 先搞明白:Bash 和 POSIX 之间到底是“父子”还是“竞争”
我接触 Bash 这么多年,见过最普遍的误解,就是认为“Bash 就是 Linux 上的 shell,跟 POSIX 这个生僻词没什么实际关系”。直到你写的脚本从 Ubuntu 挪到 macOS,或者从交互式终端跑进 cron、从 bash 切到 sh,问题才会像火山一样爆发出来。这章标题叫“Bash and POSIX”,看上去是标准文档里最干巴巴的一节,但实际踩坑的时候,你会发现它几乎解释了你遇到过的所有诡异行为。
先说结论:Bash 是 POSIX shell 规范的一个超集实现,但它不是 POSIX 标准的“克隆品”。POSIX 是一套由 IEEE 制定的操作系统接口标准,其中 Shell and Utilities 部分(也就是 POSIX.1-2017 的第 2 章和第 3 章)定义了一个名为 sh 的命令解释器应该具备哪些语法、内建命令和行为。而 Bash 在完成“符合 POSIX”这个基本要求之后,额外加了一大堆自己的扩展特性:[[ ]]、关联数组、进程替换、let、source、$'...' 转义、&> 重定向……这些好东西让日常操作极其顺手,但副作用是,你的脚本一旦用上了它们,就不再是严格意义上的“POSIX shell 脚本”。
有一个很形象的类比:POSIX 定义的是“标准普通话”,Bash 是带着大量方言的普通话。你在日常交流里说方言没问题,但如果你拿着方言稿子去参加全国播音员考试,评委们大概率会给你打叉。POSIX 就是那位评委。sh(在 Ubuntu 上是 dash,在不少 UNIX 发行版上是其他实现)只会念标准普通话,它读不懂你的方言,于是你在 bash 里跑得好好的脚本,换到 sh 一执行,满屏 not found。
这个关系决定了你写脚本时的决策路径:如果你的脚本只在 bash 环境下运行,且你控制运行环境,那尽管用高级特性;但如果你想让它能在任何 POSIX 兼容系统上跑起来,你就必须学会“在 bash 里自动切换成普通话模式”。Bash 官方考虑到这种需求,专门提供了一种兼容模式,也就是这一节的核心——POSIX mode。理解这个模式,等于同时理解了 Bash 的两幅面孔。
还有一层关系容易被忽视:Bash 在不少系统上会被调用成 sh。比如在 macOS 上,/var/select/sh 可能指向 bash;在基于 systemd 的许多 Linux 发行版里,/bin/sh 指向 dash;但在某些没装 dash 的系统上,/bin/sh 就是 bash 的一个符号链接。同样一个可执行文件,用不同名字被调用时,会采取截然不同的启动行为和语法规则。这就是为什么你在脚本第一行写 #!/bin/sh 和写 #!/bin/bash,可能跑出完全不同的结果——不是内核在作怪,而是同一个 bash 程序在检测到自己被叫成 sh 后,主动收敛了方言。
1.1 Bash 不是 POSIX shell 的“克隆”,而是一个超集
如果从零开始看这个问题的初学者,建议先建立一个三维模型:
- 第一维:POSIX 规范规定了一套最小功能集。比如
for循环、case、while、函数定义、变量展开、重定向、管道等,这些是任何自称兼容 POSIX 的 shell 都必须支持的。 - 第二维:Bash 实现了这套功能集,并且几乎全部兼容。理论上,一个符合 POSIX 规范的脚本,拿到 bash 里执行,基本不会出问题。
- 第三维:Bash 在实现之上叠加了大量扩展。这些扩展就是“方言”,它们不属于最小集,但在 bash 里用着非常爽。
也因为这样,Bash 被视为“POSIX 超集”。它不跟 POSIX 竞争,而是高于它、扩展它。同样的道理,zsh、fish 这类现代 shell 也是超集,不过它们扩展的方向不同。你不需要在 Bash 和 POSIX 之间二选一,你需要的是:了解哪些能力来自 POSIX 的“标准层”,哪些来自 Bash 的“扩展层”,然后根据需要决定到底用哪一层。
1.2 你在 Linux 上跑的 Bash,和 POSIX 有什么关系
很多人在 Linux 上敲 bash,根本没想过背后还有标准。Linux 本身并不强制要求某个 shell 必须符合 POSIX,但你运行的大多数系统脚本、init 脚本、软件的安装脚本,都默认 shell 具备 POSIX 规定的行为。比如 Debian 体系里 /bin/sh 指向 dash,而 dash 就是一个坚持 POSIX 的最小化 shell,它不搞花活,所以系统启动脚本才能在任何环境下稳定执行。
Ubuntu 上 sh 显示为 dash 这个设计不是拍脑袋想出来的,它恰恰是为了“避免 Bash 扩展语法混进系统基础脚本”。你想,如果 /bin/sh 是 bash,那么系统维护者或者第三方开发者写启动脚本时,很容易偷懒用上 [[ ]]、数组这些 bash 特性,一旦哪天底层换成别的 shell,整套系统就崩了。把 /bin/sh 固定成一个严格 POSIX 的轻量级 shell,等于从根上逼着大家写“标准普通话”。
所以,聊 Bash and POSIX 绝不只是理论问题。它直接决定了你在什么场景下可以放心使用 [[ ]],什么场景下必须退回去用 [ ];决定了你在命令行里可以用进程替换,但写进某个通用安装脚本却可能被环境拒绝。理解这些边界,才能解释第 3 章里那些五花八门的报错到底从哪来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. POSIX 模式最容易被忽略的差异:一份基于实测的行为对照
Bash 提供了三种方式进入 POSIX 模式:
- 以
sh的名称启动 bash(自动进入 POSIX 模式)。 - 启动时加
--posix参数。 - 运行时执行
set -o posix。
进入之后,Bash 不会变成另一个程序,而是会调整一批默认行为,尽量减少与 POSIX 标准的差异。注意,它是一门“尽量兼容”而不是“完全等同”的语言。很多 Bash 扩展语法在 POSIX 模式下依然能用,因为它们的支持机制藏在词法分析器里,没法通过一个开关彻底关闭。真正可靠的可移植性检测,是把它拿到真正的 sh(比如 dash)下面跑一遍。
2.1 开启 POSIX 模式的方式与检测方法
用命令行直接测最直观。先看看当前 bash 版本和基本状态:
bash复制$ bash --version | head -n 1
GNU bash,
