test, [, 和 [[ (2020)
Shell 程序员在 POSIX 标准的 `[ / test` 与 Bash 特有的 `[[` 之间权衡条件判断,指出细微的语法规则、引号陷阱和布尔逻辑怪癖都可能轻易引入 bug。许多人主张:当脚本必须运行在多样或受限的系统上(如 BSD、嵌入式设备、极简 Docker 镜像)时,应坚持使用可移植的、兼容 Bourne 的 `sh`;而另一些人则在受控环境中更偏好 Bash、zsh 或 ksh 的便利特性。讨论还延伸到复杂 shell 脚本是否应被 Python 或 Perl 之类的“真正语言”替代,ShellCheck 和 `set -euo pipefail` 等工具的作用,以及如何在长期可移植性与开发者生产力之间取得平衡。
Shell 选择与可移植性
- 主要分歧在于“直接用 Bash/[[”和“目标是 POSIX sh。”
- 支持 Bash 一方:
[[更清晰也更强大;他们控制的很多环境本来就有 bash、zsh 或 ksh。很多情况下安装 bash 被视为几乎不费力。 - 可移植性一方:bash 并不总是存在(BSD、嵌入式系统、极简 Docker 镜像、OpenWRT、只有 busybox 的环境、受限制的客户机、带古老 bash 的 macOS)。
- 当脚本要在未知或遗留系统上运行时,编写 Bourne/POSIX sh 被描述为职业成熟的一部分。
- 也有人认为可移植性是有成本的,不应成为普遍目标;对于内部脚本,依赖 bashisms 是可以的。
[, test, 和 [[
[,test是 POSIX 标准,通常是二进制程序或内建命令;[[是 shell 特有的(bash、zsh、ksh 等)。- 有几个人更喜欢用
test而不是[,以强调它是一个命令而不是语法;[的闭合]以及那种伪装成“语法”的样子被看作误导性的技巧。 - 也有人说,如果你已经决定使用 Bash,那么
[[更可取,因为语义更安全(例如正则、模式匹配、更少的引用陷阱)。 - 有些人坚持即使 shell 支持
[[也只使用 POSIX 特性;另一些人则认为这是对可移植性的无谓“膜拜”。
常见坑和技巧
- 最大的坑:
test/[中未加引号的变量(空值或包含空格的值、单参数行为)。反复出现的建议是:“给变量加引号”,并使用 ShellCheck 和set -euo pipefail之类的工具。 - 把
&&/||当作if的简写很流行,但如果&&后面的命令失败,其失败语义会不同。 test内的-a/-o被指出已过时;更推荐用独立测试串联&&/||。- 布尔逻辑很别扭:没有真正的布尔类型;替代方案依赖退出码或数值 0/1。
何时使用 shell,何时使用其他语言
- 几个人认为复杂逻辑应该迁移到“真正的”语言(Python、Perl、Node/zx 等);shell 最适合做胶水、编排和集成测试。
- 也有人为 shell 辩护,认为只要纪律严明,它就是一门“真正的语言”,但他们仍不会用它来启动大型项目。
标准、GNU 风格扩展,以及生态怪癖
- 争论是否应坚持 POSIX,还是利用 GNU/Bash 扩展(
pipefail、GNUgrep/sed选项等)。 - POSIX 被视为对互操作性有价值,但也被认为落后于现实需求。
- 还有一些旁注:Debian/Ubuntu 上
/bin/sh指向 dash、macOS 上 BSD 工具与 GNU 工具的差异、autoconf 对[]的特殊处理、$_的奇怪行为,以及 busybox 软链接与硬链接布局。