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、GNU grep/sed 选项等)。
  • POSIX 被视为对互操作性有价值,但也被认为落后于现实需求。
  • 还有一些旁注:Debian/Ubuntu 上 /bin/sh 指向 dash、macOS 上 BSD 工具与 GNU 工具的差异、autoconf 对 [] 的特殊处理、$_ 的奇怪行为,以及 busybox 软链接与硬链接布局。