POSIX 不是 Shell
POSIX Is Not a Shell

原始链接: https://alganet.github.io/blog/2026-06-28-12-POSIX-Is-Not-A-Shell.html

“POSIX”是一项技术规范,而非程序。尽管开发人员常为“POSIX shell”编写脚本以确保可移植性,但这一术语具有误导性,因为不同的 shell(如 bash、dash、ksh 等)对标准的解读并不一致。甚至像 `echo` 这样基础的命令,在不同实现中的表现也各不相同,这通常源于 POSIX 规范自身所允许的历史差异。 作者认为,关于存在通用“POSIX 兼容”shell 的普遍看法是一个误区;相反,Shell 脚本正深受类似于自然语言的“方言”问题困扰。仅仅因为脚本能在本地环境运行就宣称其“具有可移植性”是不够的。真正的可移植性需要通过在不同的 shell 实现中进行严格验证。 为了解决这一问题,作者开发了 *shell-docs*,这是一个能在 14 种不同的 shell 中机械化测试代码的工具,旨在记录各种差异行为并确认兼容性。归根结底,本文警示人们不要被 `#!/bin/sh` 的 Shebang 所带来的虚假安全感所迷惑。可移植性不应是基于标准作出的假设,而应是通过在脚本实际运行的 shell 中进行测试后所得出的经由验证的结果。

这篇 Hacker News 的讨论围绕着《POSIX 不是 Shell》一文展开,该文探讨了编写可移植 Shell 脚本的细微差别。 评论者强调了该文章有效的营销策略:作者指出了一个常见的编程问题,并引入了他们自己的工具来帮助开发者测试 POSIX 可移植性。 然而,讨论也引发了关于“可移植性”定义的争论。一位用户认为,依赖非 POSIX 定义的行为会破坏兼容性,并将其比作编写 C99 代码。其他人则反驳说,虽然这种严苛在技术上是准确的,但在实际应用中往往边际效益递减。对话最后对作者的工具提出了批评,指出如果软件需要 Docker 才能构建,那么它就无法在不同的 Linux 环境中实现真正的可移植性。
相关文章

原文
POSIX Is Not A Shell

Alexandre Gomes GaigalasJune 28, 2026

When someone says "write it in POSIX shell for portability," they mean well.

POSIX is a specification. Not a program. The thing that actually runs your script is bash, dash, ash, ksh, yash, or one of a dozen others. They each implement POSIX with their own gaps, extensions, and historical accidents.

Here is a small experiment. One line, no flags, no functions, nothing exotic:


echo "C:\new"

On bash, ksh, and a handful of others, you get back exactly what you typed:

C:\new

On dash (which is /bin/sh on Debian, Ubuntu, and Alpine) the same line prints:

C:
ew

dash's echo interprets \n as a newline. bash's does not. Run it across shell-versions and the shells split almost evenly: about half treat the backslash literally, the other half expand it.

docker run --rm alganet/shell-versions:all -c 'echo "C:\new"'

POSIX does not break the tie. The standard explicitly leaves echo's treatment of backslash escapes implementation-defined, and encourages you to use printf instead. So "POSIX echo" is not a behavior you can write a script against. It is a documented disagreement.

This is not a contrived edge case. echo is the first command anyone learns. It is what "POSIX compliant" means in practice: compliant with the parts the spec actually pins down, on the versions that happened to ship.

The Problem With Dialects

Natural languages have dialects. Brazilian Portuguese and European Portuguese are both Portuguese. A native speaker of one can understand the other with effort, but they are not the same. You cannot write Portuguese and expect it to work identically everywhere.

Shell scripting has the same problem. Bash is not the shell. It is a shell with a specific set of behaviors, many of which are not in POSIX and some of which contradict implementations that are technically more compliant (like dash).

The community pretends otherwise. We say "sh script" and mean "bash with -e -u -o pipefail." We say "POSIX compliant" and mean "it worked in CI." We say "portable" and mean "it ran on the two machines I tried."

What Validation Actually Looks Like

I have been building shell-docs, a cross-shell reference that validates each documented feature across 14 shells. The process is mechanical: write an example, run it on every shell, record the output, check for divergence.

Most features work the same. Some do not.

$# - the count of positional parameters. Works everywhere, no surprises.

local - not in POSIX at all. Every shell has it anyway, with different scoping rules.

$(( )) - arithmetic expansion, universally supported. Division by zero: varies by implementation.

[[ ]] - not POSIX, not present in dash. If your script uses it under #!/bin/sh, it will silently fail on every system where /bin/sh is dash (Debian, Ubuntu, Alpine).

The ones that diverge are not bugs. They are accumulated decisions from decades of independent implementations, each targeting a slightly different point in the spec's history: 1988, 2001, 2017.

The Honest Version of "Portable"

Portable means: tested across the range of shells it will actually run on.

The tool shell-versions tracks what version of each shell ships with major distributions. At time of writing, /bin/sh is:

  • dash 0.5.12 on Ubuntu 24.04
  • bash 3.2.57 on macOS (unchanged in fifteen years; GPL v3 kept bash 4.x out of the base system)
  • busybox ash on Alpine
  • ksh93 on some enterprise Linux distributions

These are not the same program. They are not even the same age.

If you write #!/bin/bash and mean it, that is fine. At least it is honest. Write #!/bin/sh and you are promising something you should verify.

Verification Is A Habit

The validation harness in shell-docs runs each example against all 14 shells in isolation, captures stdout and exit codes, and compares them against a known-good table. When a new shell version is released, you run it again.

This is not complex. It is just work that does not happen by default.

The payoff is that "it works in POSIX sh" can mean something concrete: it works in the fourteen shells I tested, and here are the results. That is a different claim than "I ran it in bash and it looked fine."

POSIX is not a shell. It is a promise about what shells should do. Verification is how you find out which ones kept it.


CC BY-NC-SA 4.0

联系我们 contact @ memedata.com