Execution model: three tiers, zero forks
POSIX fork() has no equivalent on Win32. MSYS2/Cygwin's answer is to
fabricate it at the syscall layer — expensive, fragile, and famous for its
quirks. niubash's engine (rubash) takes a different road: replicate fork's semantics,
not its mechanism. The result: subshells and command substitution at zero
process-creation cost, while everything a script can observe — exit codes, fd inheritance,
shared offsets, signal disposition — matches GNU Bash.
The three-tier model
| Tier | Mechanism | Which face of fork it covers |
|---|---|---|
| 1. In-process subshells | ( list ) and $( ) never create a process: ShellState::clone copies variables, aliases, functions, traps and history; the subshell ends and the copy is discarded. | fork's copied "memory face". |
| 2. A real-handle fd table | Slots hold native Windows HANDLEs; handles duplicated via DuplicateHandle share the same kernel file object — hence shared file offsets, exactly POSIX "dup shares the open file description" (proven with a POC before landing). fork_table() copies the whole table handle by handle. | fork copies the fd table but not the file object. |
| 3. Real processes only at boundaries | External commands and pipeline members start via CreateProcess + os_pipe; background jobs and coprocs each hold a real process, job control builds on Job Objects, SIGCONT goes through ResumeThread. | Where fork is genuinely unavoidable: the process boundary. |
In other words: what can avoid creating a process (subshells, command substitution) never creates one; what must create a process (external commands, pipelines, background jobs) takes Win32's native path directly. No "simulated POSIX" layer sits in between.
62 real builtins + 12 inlined coreutils + pipeline-inline arms
Command words dispatch in three tiers. 62 real builtins execute in-process
(all 61 of GNU Bash 5.3's builtins covered, none missing, plus an in-process
env — coreutils semantics, sorted output, -0 support). There are also
hidden fast-paths (sleep/dirname/basename/uname/arch):
simple argument forms run in-process, complex forms fall back to the external command, and
introspection (type/command -v/enable) reports them as
external commands — same as GNU Bash reports.
| In-process tier | Roster | Notes |
|---|---|---|
| Inlined file commands (12) | pwd printf mkdir touch chmod cp rm rmdir cat sed mkfifo tty | High-frequency file commands execute in-process inside the engine (aligned to GNU coreutils stdout/rc; complex forms with flags fall back to the external command). |
| Pipeline-inline arms | true : false echo printf trap head cat sed grep wc tr | Matched by the command's position in the pipeline: a hit runs in-process; only refused forms (e.g. grep regex metacharacters) fall back to the external binary. |
Combined, a pipeline like echo | cat is entirely zero-process.
The roster is not a documentation slogan: the builtin whitelist and the fast-path list are
guarded by cargo-test documentation-sync tests — change either side of code or docs without
the other and the build fails.
中文原文 · Chinese original
命令词按三层分发。62 个真内建进程内执行(GNU Bash 5.3 的
61 个内建全覆盖、无缺失,外加进程内 env——
coreutils 语义、排序输出、支持 -0)。另有隐藏 fast-path
(sleep/dirname/basename/uname/arch):
简单参数形态进程内执行,复杂形态回退外部命令,而 introspection
(type/command -v/enable)报告为
外部命令——与 GNU Bash 的报告一致。
组合起来,echo | cat 这样的管道一整个零进程。
名单不是文档口号:内建白名单与 fast-path 清单由 cargo test 的
文档同步测试守护——代码与文档任何一侧变动,另一侧不改则构建失败。
Observable behavior: zero difference, proven
"Implemented differently" is not a euphemism for "behaves differently". The observable surface
of this model — exit code propagation, fd inheritance and shared offsets, signal disposition —
is exactly what GNU's 83 upstream test corpora cover: 83/83 passing, 82 suites byte-identical
(see the compatibility overview). As corroborating
evidence, the engine also runs the core loop of the bashdb debugger end-to-end
(list/step/next/where/continue/quit) and git's own test framework t/test-lib.sh —
both extremely sensitive to execution-model details.
For the performance payoff of this design (subshells, command substitution, background jobs) and the honest parity floor of external processes, see the performance profile.