全部更新日志
聚合自各仓库 CHANGELOG · 拉取于 2026-10-24 · 共 37 个版本 · ← 只看每产品最近 3 个版本
-
niubash 1.4.4 2026-10-22
Changed
- WinuxCmd pin v1.1.19 → v1.1.20(#507,双 SHA-256 取自官方 release 资产 digest):裸 MSYS 盘根操作数(`ls /e`)跨 applet 面经共享 argv 边界解析(#1274);`env --file`(#1277)与 `csplit -f/--prefix`(#1278)同族折叠;每个 applet 的 `--help` 结尾带 unixwin.dev 门户链接(#1273);unity 构建在含正则元字符的检出路径下不再中止(#1281)。
Fixed
- `ping`/`netstat`/`tracert` were `command not found` (rubash#998/#1000): System32 ships these applets with UPPERCASE on-disk names (`PING.EXE`, `NETSTAT.EXE`, `TRACERT.EXE` — `HOSTNAME.EXE` too), and the engine's byte-exact stem contract (#799) rejected every lowercase invocation. The PATH fold arm now compares the stem ASCII-case-insensitively (byte-equal spellings still win; the resolved spelling is always the on-disk one) — parity with CMD, PowerShell and msys bash. The interactive crash on `${v^^}`/`${v:1:3}` input (niubash#503) ships in the same engine.
- MSIX Store 版静默降级(#504/#505):旧 NSIS 安装残留的 `WINUXCMD_PATH`/`WINUXSH_ROOT` 环境变量会让 packaged 进程解析到旧安装树;package identity 下现按外来安装忽略。
- Upgrades now reconcile the bundled package shims (`winuxcmd\usr\bin\gawk.exe`, `awk.exe`): `wpm links rebuild` cannot reach them on a bundled install because the product ships no `.wpm` state, so replacing `winuxcmd.exe` left those names on the previous image — the 1.4.1 stale `awk.exe`/`gawk.exe` residue of #468. The installer runs `niu --relink-bundled-shims --force` after the wpm rebuild, and shell-startup activation runs the same reconcile: missing or stale declared names are parked and relinked to the current `winuxcmd.exe`, extracted copies are upgraded to real hardlinks, and names owned
更新
- Release carrying the rubash 1.4.3 engine (command-stem case fold — `ping`/`netstat`/`tracert` reachable again — plus the interactive `${v^^}` crash fix), the WinuxCmd v1.1.20 pin, and the MSIX foreign-env hardening.
-
rubash 1.4.3 2026-10-22
新增
- 仓库 logo(#988):青色 rotor-r 引擎标识。
性能
- splits_unquoted no-IFS 短路重落地(#995/#1002):#992 门因空展开折叠语义回退(无引号空展开把一个字段折叠为零、词消失),重落地版加入 `!expanded.is_empty()` 准入;splits_gate 4 万对抗输入 × 5 IFS 态差分网兜底,Linux 13/13 upstream 套件与基线逐字节一致。
- assoc 交错访问固定成本(#914):`k[i]`/`k$i` 交错写读尾部的每操作固定开销削减;export/unset 交错循环二段身体削减(#984/#986/#920 后续)。
- 信号提示事件每 8 个命令边界采样(#682):信号 hint event 从每命令一次降频。
- `[ $VAR -op N ]` 整数比较快展开:测试面热路径。
- 裸 `` 全局排序、mapfile 批量拆分/存储、数组元素 quote 单分配等 #607-#609/#661-#680 系列切片全部落定(详见下方各条)。
修复
- 命令词干大小写折叠(#998/#1000):System32 的盘上真名全大写(`PING.EXE`/`NETSTAT.EXE`/`TRACERT.EXE`/`HOSTNAME.EXE`),#799 的词干字节精确契约使小写 `ping`/`netstat`/`tracert` 全部 `command not found`(`PING` 大写反而能跑、`which`/`command -v` 两张脸不一致)。fold 臂改为 stem ASCII 大小写折叠(字节相等仍优先;无扩展名盘名保持字节精确;解析拼写恒报盘上真名)——与 CMD/PowerShell/msys 全生态一致;#799 的 harvest 误抬面(小写 `r` 抬 `R.exe`)按 Windows 生态惯例重新接受。tests/issue799 套件改写为新契约并新增 System32 大写盘名回归。
- 行编辑器 `${v^^}`/`${v:1:3}` 崩溃(niubash#503,c143699c):下标 walker 里不可达的 `LEX_WASDOL` `${` 臂被以裸 `{` 语义消费,交互输入含上述序列触发 6 GiB 分配失败(MSIX 与 NSIS 1.4.1 双复现)。删除该臂。
- cli_tests 与宿主 Winuxsh 安装面隔离(#996):测试 spawn 不再被宿主安装的环境变量污染。
-
WinuxCmd 1.1.20 2026-10-22
Added
- `--help` footer: every applet's `--help` output now ends with the
- unixwin.dev portal link, so the documentation surface is reachable from
- the binary itself (#1273). The README and the guides link the portal too
- (#1271).
Fixed
- bare drive-root operands (`ls /e`, `cp /e ...`, every applet that
- touches the filesystem outside the shared `native_path` boundary): a bare
- MSYS drive root (`/e`, `/e/`) bypassed the drive-dialect recognizer and
- decayed to a cwd-drive-relative path, so resolution depended on the
- caller's drive. Glob expansion, digest open-failure probes, `mktemp` and
- the classification probes of head/tail/sort/nl/fmt/fold/comm/join/paste/
- cut and the *sum family now run on the resolved spelling; `df` keeps the
- operand spelling for diagnostics. `sort -T` normalizes the dialect hint at
- temp-dir construction and at chunk cleanup, so spill chunks no longer leak
- onto the hinted drive root (#1230).
- env --file/-f: the operand was opened with a raw `ifstream`, so MSYS
- drive-dialect spellings (`/e/...`) decayed to a cwd-relative path and
- missed the named file. `read_env_file` resolves the operand once through
- `normalize_api_operand`; the directory-classification probe runs on the
- native spelling while diagnostics keep the caller's text (#1275).
- csplit -f/--prefix: same family — the output-operand prefix reached
- `fopen` unfolded, so `/e/prefix-` created `prefix-00` on the cwd drive.
- The prefix folds through the shared boundary before any creation
- (#1276).
- build: the unity build matched command sources with
- `ABS_SOURCE_FILE MATCHES "${COMMANDS_DIR}.*"`, building the regex from a
- literal path. On checkouts under paths containing regex metacharacters
- (e.g. `D:\code\C++Files\...`) the pattern cannot compile and configure
- aborts with "Regular expression ... cannot compile". The metacharacters
- are escaped before the match (#1281).
-
niubash 1.4.3 2026-10-21
Added
- Launcher accepts `-EncodedCommand` and `--ec` spellings for the verbatim base64 handoff, with `v1:` rewrite-guard semantics preserved across all spellings (niubash#477).
- `niu --help` and `--version` close with the unixwin portal lines (niubash#490).
Fixed
- Interactive multi-line groups no longer wedge the REPL: a stray `)` inside an open group now ends the gather with a single GNU-style diagnostic and rc 2, returning to the prompt (engine reader rubash#955 + host repl niubash#484; #482).
- Bare drive designators (`D:`, `cd D:`) now converge on the engine dispatch across the host fast path, grouped route, and piped `niu -i` (rubash#953; #481, #483).
- `unset` of an inherited environment name now clears the process-env copy (9-probe matrix realigned to GNU; #943 rework of #917).
- Marker-zone literals in `-c` operands pass through verbatim — the respawn exclusion is double-escape only (rubash#952; #948).
- `find_user_command`'s Windows listing negative cache now invalidates when candidate files appear, so newly installed commands are immediately visible (rubash#951).
更新
- Release fixing the interactive gather wedge (P1), converging bare-drive behavior, hardening installer guards, and upgrading the vendored bash engine to patch 20. Carries WinuxCmd v1.1.19 (gawk BINMODE argv-prefix adapter) and the rubash 1.4.2 engine.
Engine
- Vendored bash upgraded from Bash-5.3 patch 15 to patch 20 (rubash#957): readline multibyte prompt redraw (17), long-prompt crash fix (18), high-byte identifier guard (19), SIGWINCH prompt recompute (20), macOS pipe sizing (16).
- Marker-boundary divergences closed: E400-aware sentinel decode family for marker output (rubash#947, #840) and catalogue batches with an exhaustiveness gate (rubash#944, #746).
- Environment: `export`/`unset` per-op cost reduction (rubash#943 env fast-path family).
Security / hardening
- Installer-surface guards: constant-fold coverage for `{syswow64}`/`{sysnative}`/`{autocf}`, `{sys}` name-boundary enforcement, and mutation vectors pinned by test (niubash#472).
- The staged gawk pipeline gate now asserts raw output bytes — LF contract is enforced by the gate once WinuxCmd ships the BINMODE adapter (niubash#479).
-
rubash 1.4.2 2026-10-21
性能
- expand 面:多字面量快路径镜像加固、walker 入口链去重、procsub 扫描白名单跳过;vartable P1a 脏写收口。
修复
- env unbind 丢弃继承的进程 env 副本(#917 rework,#943):unbind 面残留清理。
- read 对 fd-0 自 dup 走普通 fd-0 通道(#975)。
- `${...}` IFS-mark 复制臂使用 cook 自身的 dolbrace span(#977):防逐臂特判回归。
- Windows lane 补 `cargo test --lib`(#951),listing miss 以直查探针证明(#392 后续)。
变更
- BASH_COMPAT 广告位跟随 vendored PATCHLEVEL 20(#957/#895 链)。
-
WinuxCmd 1.1.19 2026-10-21
Fixed
- chgrp / chown: #1249 CR follow-ups (#1262). `//e/..`-class operands
- (the POSIX two-slash rule keeps them rooted at `//e`, never `/`) now
- report `cannot access '//e/..': No such file or directory` from both
- commands - chown printed the Win32 invalid-name text where chgrp and
- the MSYS oracle print ENOENT. `chgrp -R` propagates a failing
- descendant through `process_directory` like chown's
- `process_recursive` does, instead of still exiting 0. The operand
- spelling convention is now documented in DOCS/gnu-coreutils: messages
- quote the operand verbatim, so `///` prints as `///` where MSYS shows
- `/` after its own argv-layer collapse (same refusal, display-only
- divergence).
- opt/ payload argv adapter (gawk CRLF): `usr\bin\<cmd>` shims and the
- `winuxcmd <cmd>`/`<cmd>.exe` forwarding path now splice a fixed argv
- prefix ahead of the forwarded arguments for commands that need one. The
- only current entry is `awk`/`gawk` -> `-v BINMODE=3`: the bundled
- ezwinports gawk is a text-mode mingw build whose CRT rewrote every `\n`
- as `\r\n` on write and folded `\r\n` into `\n` on read, while every
- winuxcmd applet does binary stdio (unixwin/niubash#467). That build
- carries gawk's BINMODE feature but ignores the `BINMODE` environment
- variable, so the command-line switch is the only lever; splicing it
- first keeps a user's own `-v BINMODE=...` authoritative (later
- assignments win). The `bin\<cmd>` PATH-entry link for adapted commands
- now hardlinks a forwarder (`winuxcmd-shim.exe`, or `winuxcmd.exe` on an
- installation that predates it) instead of the payload itself, and both
- forwarders probe the installation root at the `bin\` depth as well as
- the canonical `usr\bin\` one.
- `winuxcmd <verb>` forwarding: `winuxcmd <name> args` forwarded the
- verb itself as the payload's first argument, so `winuxcmd gawk '{print}'`
- failed with "cannot open file '{print $1}'" and every `layout: shim`
- payload reached through the verb form saw a corrupted argv. The verb
- token is now skipped when the forwarded command line is rebuilt.
-
niubash 1.4.2 2026-10-20
Changed
- Release chain: the bundled rubash engine pin moves to `a83a09d32b4a4f953d29af0acc0e781b542206b8` (`1.4.1`) and the bundled WinuxCmd pin moves to `v1.1.18` (#466).
Fixed
- No more transient console windows when spawning external commands from a windowless or ConPTY host: the bundled WinuxCmd v1.1.18 `winuxcmd-shim` launcher suppresses orphan console creation when the parent has no visible console (#466, WinuxCmd#1282), rubash suppresses console windows on child process spawns from windowless parents (rubash#909), and niubash allocates at most one hidden console per session while adopting/suppressing orphan consoles across `fd::spawn_whitelisted`, `git_prompt`, completion helpers, plugin source git clones, and setup wizard probes (#462, #455).
- `argv[0]`-marked login shells (`-bash`, `-niu`) preserve `shopt login_shell` as `on` across `reset_shell_options` (#465, rubash#911).
- Unix platform shell discovery resolves the installed shell binary by platform binary name (`niu` on Unix vs `niu.exe` on Windows) (#463).
- Golden-journey release audit fixes: ConPTY gallery pseudo-console window preservation (#455), sandbox hygiene and deterministic test gates (#442, #447, #449).
- Release packaging: the product zip now carries `winuxcmd-shim.exe` beside `winuxcmd.exe` so upgrades replace a stale package-shim forwarder (the actual #466 fix payload) instead of leaving the 1.1.x buggy binary in place, and the x64 preinstall leg is fail-closed — a failed pin check, package install, or shim materialization stops the release instead of silently shipping a reduced bundle (#468). The gawk `unixwin2` artifact 404 that silently skipped the 1.4.1 gawk preinstall is fixed at the source (wpm-artifacts asset name).
更新
- Release addressing P0 transient console window pop-ups across WinuxCmd shims, rubash child spawns, and niubash runtime helpers, plus the v1.4.1 golden-journey release audit fixes.
-
WinuxCmd 1.1.18 2026-10-20
Fixed
- winuxcmd-shim: `winuxcmd-shim.exe` previously gated child creation
- flags on `GetConsoleMode(GetStdHandle(...))` and passed
- `DETACHED_PROCESS` whenever stdio was redirected (`python - <<'EOF'`,
- pipes, agent runs). That stripped the inherited console
- (`GetConsoleCP() == 0` in the payload), causing every console-subsystem
- grandchild (`python` -> `git.exe`, `node` -> child tools) to allocate a
- fresh `conhost.exe` and flash a visible console window on the desktop
- (unixwin/niubash#466). The shim now releases only a sole-owned *visible*
- auto-allocated console (`IsWindowVisible` + `GetConsoleProcessList == 1`)
- when stdio is non-console, keeps any inherited or hidden console
- (`GetConsoleCP() != 0` -> `flags = 0`), and uses `CREATE_NO_WINDOW`
- (never `DETACHED_PROCESS`) when `GetConsoleCP() == 0`.
- mkfifo / mknod: FIFO/device-node path operands resolved against the
- cwd drive (#1235, #1230 sweep). The raw `/e/p` spelling reached the
- existence probe and `create_winux_fifo_w`, and Win32 roots a leading `/e`
- on the CURRENT drive, so from a `C:` cwd `mkfifo /e/p` probed
- `<cwd>:\e\p` (ENOENT for a real `E:\p`) and would have landed the marker
- beside the cwd; `mknod /e p` from a `D:` cwd really created a `D:\e`
- marker file. Operands now fold once through the shared
- `make_api_path_operand` boundary before any filesystem call;
- `/cygdrive/e`, `/mnt/e` and bare `/e` (which now reports "File exists"
- like MSYS) share the fold, and messages keep the operand spelling as
- typed.
- chgrp / chown / chcon: `--reference` and target file operands
- resolved against the cwd drive (#1241, same sweep). An unfolded
- `/e/...` spelling made `GetFileAttributesW` /
- `GetNamedSecurityInfoW` / the `WRITE_OWNER` open answer for
- `<cwd>:\e\...`, so existing `E:` files were reported as missing from
- any other cwd, `chgrp -R --preserve-root /e` missed the failsafe and
- descended into `<cwd>:\e`, and chown/chcon globs enumerated the wrong
- drive. Security lookups, attribute probes, the recursive walks and the
- glob pattern all fold through the shared boundary first (children
- re-fold from their display spelling, chmod `-R` precedent); error text
- keeps the user's spelling. The preserve-root failsafe gained the same
- fold, plus direct root matches for the spellings the operand layer
- collapses onto `/`: all-separator forms (`//`, `///` - a verbatim
- `GetFullPathNameW("//")` answers for an UNC-ish name, which let
- `chown -R --preserve-root X //` bypass the check and really walk the
- shell root) and lexically collapsing forms (`/e/..`, `/tmp/..`), the
- latter with the `(same as '/')` suffix MSYS prints; `//e/..` keeps the
- POSIX two-slash rule and resolves against `//e` like MSYS.
- date: `date -r FILE` / `--reference=FILE` probed the operand the
- same way - the raw spelling went straight to `GetFileAttributesExW`,
- so `/e/Data/ref` and its `/cygdrive/e`, `/mnt/e` spellings reported
- "failed to get modification time" from a foreign cwd for a file that
- exists on the operand's drive (#1256, same sweep; the `-f DATEFILE`
- open was fixed in #1239, this is that family's second read point in
- date). The operand folds once through the shared
- `normalize_api_operand` boundary before the attribute probe; the
- failure message keeps quoting the operand exactly as typed, like MSYS.
-
niubash 1.4.1 2026-10-19
Changed
- Release chain: the bundled rubash engine is pinned to an exact commit (fail-hard on mismatch) and dirty builds self-identify in `niu --version` (#424); the WinuxCmd bundle moves to v1.1.16 (POSIX drive-dialect fixes for mkdir/ln/stat/realpath/readlink) (#427).
- CI: the VCS prompt timing budget is relative to an interleaved spawn baseline — immune to shared-runner noise, still catches real render regressions (#423/#436).
- Engine: rubash 1.4.1 — ERR-trap nested-context semantics, patsub replacement-text escaping, subscript ANSI-C double-state law, pushd/popd readonly + message forms, `${var@P}` status-leak fix, and a package guard against build-variant directories.
Fixed
- Pasted multi-line scripts no longer swallow the typeahead that follows them: the Enter-signature probe re-injects parked keys to the console queue head, so an engine `read` after a paste-submit sees the remaining input (#417, #420, #426, #433).
- `niu -c` and stdin scripts reject a stray `)` exactly like GNU bash (rc 2, same diagnostics/line numbers) instead of silently running the subshell body (#431).
- `rbash` startup semantics match GNU: the restricted face activates after startup files in every invocation form (#413); POSIX `$ENV` `exit` now exits silently without the greeting tail (#422).
- A DSR-silent terminal keeps its shell; TAB completion commits (#418).
更新
- Healthy re-release on the pinned release chain (the bundled engine is now locked to an exact verified commit). Details in the linked issues/PRs — entries here are deliberately short.
-
rubash 1.4.1 2026-10-19
修复
- patsub 替换管道中的用户 PUA 码点 E400 转义(#843)。
- 下标 ANSI-C 跨度的双引号态 + unset 操作数引号剥除(#847)。
- pushd/popd 继承 cd 的 readonly bindpwd 裁决并指名真实的越界词(#844/#848)。
- assoc 下标在 `((...))`/`let` 入口归一(#828)。
- 复合命令自身的重定向失败诊断走 fd 表且只发一次(#826)。
- `${var@P}` 命令替换状态括号、sudo 无 handler 路由钉死(#813 五项 CR 后续)。
变更
- crate 打包护栏:target 变体目录(如 target2-baseline,1.7GB 陈旧构建树)不得混入 cargo package(crates.io 上限 10MiB)。
- `(A;B;)` 杂散闭括号族全模式钉到 GNU 5.3.0(#845)。
-
WinuxCmd 1.1.17 2026-10-19
Added
- wpm: a package entry can be an alias for another one (`alias_of`), so one
- tool has one name - `wpm install git` installs `niugit` and says so, with a
- depth bound, a cycle detector and a clear error when the target is missing.
- `wpm info` shows the forwarding; `wpm list` labels such an entry `alias`.
- wpm: a `layout: shim` command's payload is resolved from the artifact's
- `files` mapping when it is not at the `opt/<pkg>/<command>.exe` convention, so
- a payload that has to keep its own directory layout (an executable beside its
- resource directories) gets a shim that can find it.
- native path: `/mnt/X/...` operands resolve like `/X/...` and `X:\...` -
- the WSL interop spelling (#1172). WSL mounts the Windows drives under
- `/mnt`, and that spelling reached argv verbatim whenever a tool was invoked
- directly (PowerShell, cmd, a scheduler, an agent) rather than through
- rubash, whose argv funnel already pre-translates the form for every child
- (rubash#444, rubash#592). `normalize_api_operand_w` now folds `/mnt/X` to
- the MSYS `/X` spelling before the drive branch, so a single drive-letter
- arm keeps owning every POSIX drive form. The accepted shape matches
- rubash's `windows_mnt_drive_argument` byte-for-byte: a lowercase `mnt`
- literal, a drive letter of either case, exactly `/mnt/X` or `/mnt/X/...`.
- Bare `/mnt` (the WSL root) and non-drive children (`/mnt/data`,
- `/mnt/cfoo`, `/mnt/123`) stay verbatim, `/mnt/cygdrive/d` cannot fold
- twice, and the existing verbatim classes (`/cygdrive/*`, `/dev/*`, bare
- `/`, `/X...` switches) are untouched. Every tool on the shared operand
- boundary (ls, du, sed, cp, ...) picks the form up at once; under a
- configured shell root the resolution is unchanged, since the niubash#346
- shell-ns arm already mapped this exact shape.
- realpath/readlink/stat: the shared resolver behind `realpath`,
- `readlink -f/-e/-m` and `stat -L` folded only the single-letter drive
- form, so `/cygdrive/e/...` and `/mnt/e/...` stranded on the current drive
- (or, with a shell root configured, mapped under the shell tree) while the
- same spelling folded correctly for stat/test -f at the argv boundary -
- "test -f said yes, realpath said no" for one and the same operand
- (#1246, the #850 family, confirmed by the #1245 review). The resolver now
- runs the cygdrive (#1145) and /mnt (#1172) prefix folds before the
- single-letter rule, in the exact order the argv boundary does, so every
- command answers one spelling identically; a folded prefix operand is
- native-dialect input and is never re-expressed as a shell-namespace path.
- stat itself routes its operand through the shared argv boundary since
- #1245, which also made shell-namespace spellings (`stat /usr/bin` under a
- configured shell root) resolve like ls and readlink -f instead of
- reporting ENOENT.
- look: a dictionary ending in a final newline carried a phantom empty
- entry at the END of the sorted line array, and the binary search's
- midpoint could land on it - an empty string sorts before every key - so
- lower_bound jumped past the last real word and `look -f delt` printed
- nothing for a dictionary ending "…charlie\ndelta\n" (#1251). The final
- newline at EOF no longer synthesizes an entry (matching util-linux look,
- which also treats a lone "\n" file as exactly one empty entry); empty
- lines in the middle of the dictionary keep working as real entries.
- tree: the `-o` output operand did not fold through the shared
- `normalize_api_operand` boundary, so `tree -o /e/probe/out.txt` from a
- foreign cwd opened a shadow `C:\e\probe\out.txt` on the cwd's drive - or
- failed to open at all - instead of writing on the named drive (#1254,
- the write-side follow-up to #1236). The output operand folds once like
- the root operand; the open-failure message keeps quoting the spelling as
- typed.
Fixed
- shred: `shred /e/Data/secret` from a `C:` cwd handed the raw "/e/..."
- text to CreateFileW, and Win32 resolved a leading `/e` against the
- CURRENT drive (#1240). For the only data-destroying command in the
- toolset that misparse was doubly dangerous: when a shadow `C:\e\...`
- happened to exist, shred silently overwrote THAT file and exited 0 -
- the requested E:\ target stayed intact, i.e. a false success over the
- wrong victim; when no shadow existed, a perfectly good E:\ file was
- reported "cannot open". Every operand now folds through the shared
- `make_api_path_operand` boundary before the overwrite, the `--force`
- retry, the native-glob pass (a `/e/*.log` pattern expands against the
- operand drive) and the `-u`/`--remove` unlink, and the Win32 calls use
- the extended form like the other post-#1233 commands. Messages keep
- printing the operand spelling as typed; `/cygdrive/e`, `/mnt/e` and
- Windows-form spellings share the fold, and a missing operand still
- fails with rc 1 like MSYS.
- mkdir: `mkdir -p /e/Data` from a `C:` cwd really created a shadow
- `C:\e\Data` tree - the one audited misparse that writes to the wrong
- place (#1233, found by the #1230 sweep). Win32 resolves a leading `/e`
- against the CURRENT drive, so the raw operand reached
- CreateDirectoryW / `create_directories` rooted on the wrong volume, an
- existing `E:\Data` reported ENOENT instead of "File exists", and `-m` /
- `-v` answered for the shadow path. Every operand now folds through the
- shared `normalize_api_operand` boundary once on entry to
- `create_directory` (and in the `-m`/existence helpers) before any
- filesystem call; GNU-style messages keep printing what the user typed,
- like MSYS does. `/cygdrive/e`, `/mnt/e` and bare `/e` spellings all
- share the fold, and `mkdir -p /e` (bare drive root) is a silent no-op
- instead of creating `<cwd>:\e`.
- ln: `/e/...` link operands resolved against the cwd drive (#1234,
- same sweep). `std::filesystem::absolute`/`GetFileAttributesW`/
- `FindFirstFileW`/`CreateHardLinkW`/`CreateSymbolicLinkW` all rooted a
- raw `/e/Data` spelling on the current drive, so the hard-link source
- probe failed with "failed to access", `-t DIR` rejected a real
- directory, and a privileged `ln -s` wrote the link beside the cwd
- instead of on the operand's drive. Sources and targets fold through
- `normalize_api_operand` once per job (and in the directory-detection
- probes) before any Win32 call; create/removal error text quotes the
- operand spelling as typed. Messages and link text stay verbatim; only
- the API-facing path is folded.
- ldd: `/e/x.exe`, `/cygdrive/e/x.exe` and `/mnt/e/x.exe` operands were
- handed as raw text to a bare `std::ifstream`, and the CRT resolves a
- leading `/e` against the CURRENT drive - from a foreign cwd every POSIX
- spelling reported `cannot open` for a file that exists on the operand's
- drive, while MSYS ldd opens the same spelling from anywhere (#1238,
- found by the #1230 sweep). The operand now folds once through the shared
- `normalize_api_operand` boundary at `load_file`, the single point every
- caller opens through (covering the default, `--name` and `--unused`
- paths); error messages keep quoting the operand exactly as typed.
- date: `date -f DATEFILE` opened the file the same way, so
- `/e/Data/dates.txt` and its `/cygdrive/e`, `/mnt/e` spellings reported
- `No such file or directory` from a foreign cwd, and the directory probe
- that produces the "Is a directory" report ran against the shadow path
- too (#1239, same sweep). The `-f` operand folds through the shared
- `normalize_api_operand` boundary before the `is_directory` probe and
- the open; the open-failure messages keep printing the operand as the
- user typed it. Bare `/e` (drive root) now correctly reports
- "read error: Is a directory" instead of ENOENT.
-
WinuxCmd 1.1.16 2026-10-19
Fixed
- mkdir / ln: POSIX drive-dialect operands (`/e/...`, `/cygdrive/e/...`)
- reached creation and removal folded onto the cwd drive, so `mkdir /e/d`
- from a `C:` cwd created `C:\e\d`. Both commands fold operands through the
- shared boundary before any Win32 call; `ln` also quotes the operand
- spelling in `-f` removal failures, and `canonicalize_path_w` folds the
- dialect so `realpath`/`readlink -f` agree (#1231, #1233, #1234).
-
WinuxCmd 1.1.15 2026-10-19
Added
- native_path: `/mnt/X` WSL-interop drive operands resolve like `/e`
- and `/cygdrive/e` (with a required separator, so `/mnt` itself stays a
- directory) (#1172). The release archive ships the package shim and maps
- it; indexing refuses files the archive lacks.
Fixed
- echo, text tools, xxd, *sum: octal `\0nnn` escapes, CRLF CR
- retention, and `xxd -p` wrapping gained the text-marker treatment
- (#1203, #1204, #1205).
- wpm: the shim forwarder is delivered and migrated across the wpm
- lifecycle (#1207), and an update keeps two rollback copies
- (#1206, #1208).
-
WinuxCmd 1.1.14 2026-10-17
Added
- wpm: `wpm links verify` reports, by path, every command link that does not
- run the installed `winuxcmd.exe` - the diagnosis that was missing while a
- stale link could keep one command on an older build for hours without a word.
- wpm: `winuxcmd-shim.exe`, a small forwarder behind `layout: shim`
- packages. `usr\bin\python.exe` and its siblings used to be a hardlink to an
- older `winuxcmd.exe` whose only job was to spawn `opt\<pkg>\<command>.exe`,
- so every `python`/`node`/`curl`/`ssh` call paid a whole WinuxCmd startup; the
- dispatch hop drops from a measured 24.5 ms to 12.1 ms. An installation without
- the forwarder keeps the previous behaviour, and `wpm links rebuild --force`
- (which activation already runs) switches existing installs over.
Fixed
- wpm: `wpm update winuxcmd` no longer leaves a command link on the previous
- image silently. A link that cannot be deleted is parked and replaced, the
- failing link is named, the links are verified after the swap and relinked
- once, and the detached update helper writes its output to
- `.wpm\logs\self-update-<stamp>-<pid>.log` (newest three kept) instead of
- discarding it.
- wpm: the self-update keeps the newest two rollback copies of
- `winuxcmd.exe` and prunes older ones - one machine had accumulated 61.8 MB -
- and `wpm clean backup` (and `clean all`) reclaims them.
-
WinuxCmd 1.1.13 2026-10-17
Fixed
- wpm: a package upgrade now replaces files that a running program holds
- open, instead of failing halfway. 1.1.12 refused up front when it *thought* a
- destination could not be replaced, but the probe it used — opening the name
- with `DELETE` access — succeeds against a file another process has mapped as
- its image, while the delete and the overwrite that follow both fail with
- Access denied. On a real installation this meant `wpm install niugit --force`
- still failed while git's `fsmonitor--daemon` held
- `libexec/git-core/git.exe`, and it failed *after* the reconcile and the entry
- links had run, leaving the package half-updated.
- Destinations are now renamed aside (`<name>.wpm-old-<pid>`) before
- anything is written: a rename is exactly what a mapped image permits, the
- running process keeps its old bytes, and the operation is reversible. If a
- destination cannot even be renamed, the ones already parked are put back and
- the install refuses with the culprit and the processes running from it — so
- an upgrade is either complete or untouched. Once the install commits, the
- parked names are deleted; while the old process still holds them the name
- stays, and `wpm clean` removes it later.
- wpm: a superseded path the new version no longer maps is parked the same
- way instead of aborting the upgrade when a live process holds it.
- wpm: `wpm clean staging` and `wpm clean all` also remove parked
- `.wpm-old-<pid>` names left by earlier installs.
- Verified on a live installation with 28 `fsmonitor--daemon` processes holding
- the payload: the forced upgrade succeeds, `usr/bin/git.exe` and `bin/git.exe`
- both share the payload inode, and `.wpm/staging` is pruned.
-
WinuxCmd 1.1.12 2026-10-16
Added
- wpm: every package command gets a PATH entry link at
- `<root>/bin/<cmd>.exe` — a hardlink to the real binary (the payload under
- `opt/<pkg>/` for shim packages, the mapped binary in `usr/bin/` for flat
- ones), never a launcher, so nothing extra runs per invocation. The links are
- recorded in the package receipt and kept in sync by install, update,
- `wpm links rebuild` and uninstall.
Fixed
- wpm: an install is refused up front when a destination is held open by
- a running program, instead of failing halfway through. A file held by another
- process cannot be replaced on Windows at all — delete and overwrite both
- return *Access denied* — so the old behaviour could leave a package
- half-updated; an upgrade of niugit was observed with `opt/niugit` on the new
- version while the PATH entry still ran the old one. WPM now probes every
- destination before writing anything, lists the files and the programs running
- from them, and leaves the installation exactly as it was. Close the listed
- programs and retry.
- wpm: flat-layout file mappings are copied from the package's payload
- rather than the extraction directory, so a mapping that duplicates a path
- already covered by a directory mapping no longer produces a second copy of
- the package and a PATH entry pointing into `.wpm/staging` (the two
- destinations now share one inode). The staging tree is pruned after a
- successful install.
-
WinuxCmd 1.1.11 2026-10-16
Fixed
- wpm: `wpm update winuxcmd` swaps the image by unlinking the old name
- and moving the staged binary into place instead of overwriting a
- running/mapped image in place. The in-place write fails with "being used by
- another process" whenever any process holds the file (observed on a live
- install, where the update silently kept the old version), and writing under
- a mapped image is unsafe for the processes already running from it. The
- in-place path remains the fallback when the unlink itself fails.
- wpm: installing a package reconciles its own previous install first -
- destinations the new artifact no longer maps are removed and, on a version
- change, files inside still-mapped directories that the new payload dropped
- are pruned. Before, a layout change left inert leftovers behind (the retired
- shim launcher `opt\<pkg>\<cmd>.exe` after a move to `layout: flat`, or a
- stale DLL in a PATH directory from older flat packaging). Only paths
- recorded in the package's own receipt are touched, they must resolve inside
- the root, and the winuxcmd image and the running executable are never
- deleted.
- wpm: `outdated` orders versions instead of comparing strings, so a stale
- mirror advertising an older version no longer reports a
- `update available: 2.55.0.5 -> 2.55.0.4` downgrade. The order is the
- dotted-numeric core plus, when present, a Debian-style packaging revision
- (`5.4.1-unixwin2` > `5.4.1-unixwin1` > `5.4.1`) - what a repackaged artifact
- needs, since the upstream version stays and our rebuild gets a revision.
- Vendor spellings (`9.5.0.0p1`, `r42`, ...) keep the conservative "any
- difference is an update" rule.
-
niubash 1.4.0 2026-10-10
Added
- Windows Terminal-form desktop/Start-menu shortcuts on Windows 10
- (niubash#211, #290): Win10 has no default-terminal setting, so
- direct-exe shortcuts used to be hosted by conhost. When a `wt.exe`
- resolves (WindowsApps alias -> PATH -> App Paths registry) and the
- Niubash WT profile is registered, generated shortcuts take the
- `wt.exe -p Niubash` form; without WT the conhost baseline stays
- unchanged. `niu --install-wt-shortcuts` (`--update-only`,
- `--desktop|--start-menu`) is the standalone verb and the installer
- handoff, and it only rewrites links that already exist, so a declined
- desktop icon is never grown.
- `niu --cwd-state <file>` directory memory for one-shot agent
- invocations (#272): opt-in, so a chain of one-shot `niu -c` calls
- behaves like one shell — the directory recorded in `<file>` is
- restored before startup (before niu's own startup logic, relative
- script paths, and `source_non_interactive_env`) and the shell's final
- directory is recorded back after every one-shot exit path. Without
- the flag, behavior is byte-for-byte unchanged.
- `niu --encoded-command <base64>` (#271): agents and CI callers
- get a quoting-free channel — an RFC 4648 payload (padding optional,
- ASCII whitespace ignored, must decode to valid UTF-8) executes
- through the same one-shot path as `-c`, with the same
- `BASH_ENV`/`NIU_ENV` sourcing and `BASH_EXECUTION_STRING` contract.
- An optional `v1:` prefix is stripped defensively: MSYS path
- conversion rewrites arguments starting with `/`, and a base64 payload
- can start with `/`.
- Doctor timing advice (niubash#201): `niu doctor` now carries an advisory
- row pointing timing/performance-sensitive scripts at the bash 5 builtin
- `$EPOCHREALTIME` / `$EPOCHSECONDS` instead of looping on the external
- `$(date +%s%N)` — each `date` call pays Windows process creation
- (~10-30ms) while the builtin expands in microseconds with the GNU format
- `1699999999.123456`. Regression tests pin the format, second-level
- agreement with `date +%s`, live monotonic updates, and the speed gap.
- Android and OpenHarmony release-CI legs incubating (roadmap niubash#207,
- niubash#247): `build-android` links aarch64-linux-android and
- armv7-linux-androideabi against bionic (NDK r27, API 24) and ships a zip
- per leg with ELF-arch verification in place of an on-runner runtime
- smoke; `build-openharmony` is check-only for now
- (aarch64-unknown-linux-ohos is Tier 2 with host tools — real linking
- needs the OHOS SDK clang wrapper, which has no version-pinned runner
- fetch path yet, so no binary ships from that leg). Both jobs run
- continue-on-error and stay out of the release needs, the same incubation
- shape the musl leg had before it was tightened; the platform-support
- matrix documents both targets.
- Vi editing mode wired end-to-end (niubash#184): `set -o vi` / `set -o
- emacs` now switch the LIVE line editor mid-session, like GNU bash — both
- options route to one readline editing-mode state (bash
- `builtins/set.def:424 set_edit_mode` → `rl_variable_bind("editing-mode")`,
- rebindkeymap applied immediately by readline `bind.c:2092 sv_editmode`).
- The product re-reads the engine option flags at every prompt
- (`Shell::refresh_edit_mode`), rebuilds the Reedline editor on a change,
- and writes the winner back exclusively, so `set -o vi` → `set -o emacs` →
- `set -o vi` round-trips and `set -o` listings stay mutually exclusive the
- way GNU reports them. The rc line `set -o vi` works at startup (the
- editor is built after the rc resolves the mode). Every fresh line starts
- in insert mode even in vi mode (readline.c:1243 "Each line starts in
- insert mode"; reedline keeps ViMode across reads) — a line submitted in
- normal mode no longer leaves the next prompt in normal mode. The floor
- prompt renders a minimal theme-neutral vi-mode indicator (`i ` insert /
- `- ` normal, terminal default color, configurable through
- `prompt_indicators`); a claimed PS1 keeps the theme's own prompt slot
- (GNU bash ships no built-in indicator). ESC enters normal mode through
- reedline's built-in vi parser; normal-mode motions (`k` history recall,
- `dd` line kill) are covered by ConPTY journeys.
- Release-CI pre-install of external packages via wpm (niubash#189): the
- release workflow installs the packages named in
- `scripts/release/preinstall.json` into the staged WinuxCmd root with
- `winuxcmd wpm install <pkg> --root <root> --yes` (wpm ships inside
- winuxcmd.exe — no separate binary) between WinuxCmd staging and packaging,
- so the Windows release zip and installer carry a working GNU awk out of the
- box. First entry: `gawk` (GNU Awk 5.4.1; bash-it plugins and completions
- hard-depend on awk). The manifest also records the shim (`awk.exe`, a
- winuxcmd.exe hardlink that forwards to the `opt/gawk` payload — wpm's gawk
- package registers only `gawk`, and bash-it calls plain `awk`) and the
- owner-ordered exclusions as durable policy: no compression tools
- (bzip2/gzip — users `wpm install` them themselves), `goawk` forbidden
- (gawk only), and `link` forbidden forever (MSVC link.exe collision).
- Installs are fail-open: a failed package (e.g. the arm64 legs while the
- wpm index carries windows-x64 artifacts only) warns in the step summary
- and release notes, and never blocks a release; whatever DID install is
- hard-verified in the staged package (`--version` plus a plugin-shaped
- `awk '{print $1}'` pipeline through the packaged niu.exe) before upload.
- The completions corpus carries real `awk --help`/`gawk --help` transcripts;
- the embedded applet inventory goes 178 → 180 (SKILL.md quickref
- regenerated, golden-gated).
- AI agent skill bundle with `niu skill install` / `niu skill status`
- (niubash#188): the shell ships its own agent-facing guide
- (`skills/niubash/SKILL.md` — identity, capability surface, pitfalls,
- niu-vs-cmd-vs-powershell decision guidance) and installs it into agent
- skill dirs — `--target claude|zcode|cursor|all|<dir>`, default claude;
- `status` compares installed bytes against the embedded bundle and prints
- a sha256 digest; `niu doctor` gained an advisory `agent skill` row. The
- command tables inside the bundle are generated, not hand-written:
- `scripts/generate-skill.py` renders the builtin table (from a captured
- `help -s '*'` transcript), the launcher/plugin verb surface (captured
- `niu --help`), and the 178-applet winuxcmd inventory (from the completion
- assets) into GENERATED-marked regions of `SKILL.md` and
- `references/quickref.md`; `scripts/test-skill-bundle.py` golden-checks
- both the regions and the include_str! manifest (in CI and in the release
- pipeline). Releases now attach `niubash-skill-v*.zip` with the
- WinuxCmd-skill layout (one top-level `niubash/` directory).
- WinuxCmd applet completions are compiled into the shell (niubash#172 L1):
- `grep --col<Tab>` offers `--color`/`--colour` with descriptions, `ls --color`
- offers `always`/`auto`/`never`, `wpm <Tab>` lists its 20 subcommands, and
- every one of the 178 WinuxCmd 1.1.5 applets (including `[`) carries its real
- flag set with no plugin enabled. Definitions are generated from
- `winuxcmd --help` transcripts by `scripts/generate-winuxcmd-completions.py`,
- embedded from `crates/niubash-runtime/assets/completions/winuxcmd/`, and
- load as the base layer: bundle/pack and user-dir TOMLs still override per
- command. Generator fixes en route: bracket command names (`[`), headerless
- help (`top`), multi-alias specs (`pr -F, -f`), `EXIT STATUS:`/footer
- pollution of descriptions, optional-value flags (`--color[=WHEN]`) no longer
- swallow filename completion, path-valued flags decided by spec hints instead
- of description prose, and wpm `Commands:` parsing. Golden coverage:
- `crates/niubash-runtime/tests/winuxcmd_completions.rs` plus
- `scripts/test-winuxcmd-completions.py` (corpus replay must be
- byte-identical).
Changed
- The factory default prompt on Windows is now the Git Bash skeleton
- (owner adjudication 2026-10-12, #292): green `user@host`, yellow
- POSIX-persona cwd, cyan branch (+ dirty `*`) and `$ ` — the MINGW64
- environment segment is deliberately omitted (that is MinGW identity,
- not this shell's) and there is no static product tag. The branch/dirty
- segment reads the repository through git2 (owner-approved library
- dependency, Windows-target-only compile) instead of a git child
- process, so the prompt path never spawns (the niubash#145 red line):
- measured cold 39 ms, warm p50 2.5 ms / p90 3.7 ms per prompt against
- Git Bash's per-prompt `__git_ps1` fork (~100-300 ms). Claims still
- win: a PS1/theme claim displaces the floor per prompt, and `set -o
- vi` keeps its live editing-mode indicator (niubash#184). The skeleton
- follows Git Bash's own layout after live owner passes on 2026-10-14:
- two rows (info row, then the `$ ` input line on its own row) and the
- branch in MINGW64's parentheses — `(master*)`, dirty star inside.
- Default TAB completion style is now the multi-per-row grid
- (`CompletionStyle::Column`, owner rulings 2026-10-12 + 2026-10-14):
- every TAB shows the candidates at once, several per row — the look of a
- GNU-style printed listing without the silent first TAB (the literal GNU
- readline shape shipped briefly as the default and read as "dead" at the
- prompt when TAB #1 had nothing to complete; live owner feedback flipped
- it). The literal GNU shape remains an opt-in
- (`NIU_COMPLETION_STYLE=gnu` or `niu config set completion.style gnu`):
- TAB completes the longest common prefix (a lone candidate completes
- outright), and a second TAB on an unchanged line prints the candidates
- as width-adaptive columns next to the prompt and repaints it —
- implemented as a reedline `Menu` (`completion::gnu_menu`) so it renders
- through the normal repaint pipeline. The Ide popup stays opt-in
- (`ide`, also accepted: `smart`, `popup`); precedence is env > config
- file > default, and `niu doctor` shows the effective style with the
- switch commands. The setup wizard gained a completion style step
- (current value pre-highlighted) on first run and on `niu setup`
- re-runs.
- Architecture correction before release: the carapace-bin completion source
- merged from #243 is removed from the shell core. No shell hardcodes a
- third-party completion provider into its source — the provider belongs in
- the rc data layer, integrating through the standard bash completion
- protocol the shell already speaks (`complete` compspecs, `NIU_COMPDEFS`
- compdef functions, `<cmd>.toml`/`<cmd>.bash` definition files). The native
- completion layers are unchanged (embedded WinuxCmd applet TOMLs, bundle
- definitions, user completion files); `niu setup` grows an opt-in (default
- off) rc snippet for carapace-bin — the same block a bash user writes by
- hand, guarded and a no-op until carapace-bin is installed — and a new
- docs page documents the rc-based contract for any provider that speaks
- the bash completion protocol (carapace is one example). Binary-level tests
- pin the rc route: a `complete -W` compspec and a compdef hook sourced from
- `~/.niubashrc` both serve Tab candidates, and a standard `.bash` completion
- script serves flag candidates through the bash-import channel. Interactive
- Tab resolution of `complete -F`/`-C` hook functions remains an engine
- (rubash) follow-up, documented in the provider page. Zero compatibility
- debt: no 1.4.0 tag ever carried the embedded source, and the `NIU_CARAPACE`
- variable was never a released surface (niubash#243, niubash#187).
- command-not-found no longer advertises package managers by default
- (niubash#249): a typo like `win` prints only the GNU one-liner — the
- unsolicited "package search hints" block naming winget/scoop/choco is
- removed, and third-party managers are never recommended again. The
- recipe/wpm install hints for exact, known tool names (`rg`, `awk`, …)
- stay. An opt-in channel returns a wpm-only suggestion:
- `niu config set command-not-found-hint wpm` (persisted in the new
- `~/.niubash/config.toml`; `NIU_COMMAND_NOT_FOUND_HINT` overrides) — it
- suggests one `wpm search --name '<word>'` line only for
- high-confidence near-misses (dropping the word's last character hits an
- installed command or a known package name), so pure typos stay quiet
- even with the switch on. New `niu config get/list/set` subcommand surface
- manages the file.
- Release binaries are stripped (niubash#228): the release profile strips
- symbols, shrinking the shipped niu.exe.
- The pre-install manifest bundles niugit, ripgrep and fd alongside gawk
- (niubash#230), and the setup wizard detects release-bundled components as
- already-present instead of re-offering them (niubash#231).
- Documentation passes: a "Calling PowerShell from niu" page joins the
- platform docs (when to reach for powershell.exe from niu, and how)
- with a README style pass (niubash#203, niubash#253); the READMEs were
- then restructured evidence-first around quick answers and the
- five-platform identity (niubash#255); the Platform Support page
- documents the status matrix, what the HarmonyOS bash actually is, and
- HNP packaging (#225, #226, #227); and the MSYS labels (`uname` ->
- `MSYS_NT`, `OSTYPE=msys`, `MACHTYPE` `x86_64-pc-msys`) are documented
- as what they are — a compatibility persona of a pure native Windows
- build with no msys-2.0.dll/cygwin1.dll, with the measured behavioral
- boundary (niubash#248).
- One-shot `niu -c` invocations take a fast path that skips REPL-only
- initialization (prompt/theme backend, completion directory scan,
- completion-state snapshot); the history provider and managed aliases
- keep their `-c` contract, and `bash -i -c` still sources the rc.
- Measured 13.9 -> 12.1 ms in-process startup for `niu -c 'echo hello'`
- with a 29-case behavior battery at zero diff (#270).
- REPL-only commands (`self-update`, `update-niubash`, `logout`) are
- highlighted as builtins instead of unknown-token red (#281).
- The easter eggs are pruned to snake and about; the about logo is
- pyfiglet-exact (#264).
- The declared MSRV is 1.95 — reedline 0.50 in the lock requires it
- (#267).
- Installer disclosure (niubash#313): the "Create a desktop shortcut"
- task is new in 1.4.x and default-checked. Silent installs and upgrades
- (`/SILENT`, `/VERYSILENT`) never show the task page, and Inno has no
- saved selection for a new task name — so a silent upgrade of a 1.3.x
- machine runs the task with its default state and grows a desktop icon
- (rewritten in place to the Windows Terminal form by the shortcut step)
- without ever asking. Owner ruling: the behavior is intended; it is
- disclosed here and in the installer source. Interactive installs keep
- remembering an unchecked choice.
Fixed
- The self-update installer is SHA256-verified, fail-closed, and the
- release ecosystem is pinned (niubash#311, niubash#312, PR #318):
- every Windows setup.exe ships with a coreutils-style `.sha256` sidecar
- (plus an aggregate `SHA256SUMS` for the whole release — both are
- release assets, and the same digests feed the winget manifest).
- `niu self-update` downloads the sidecar published beside the same
- asset it fetched, hashes the installer, and aborts before launch on
- any mismatch — or when the sidecar cannot be fetched at all (the
- error points at the manual-download path). Release builds no longer
- taste the live ecosystem: workflow dispatches require a pinned
- WinuxCmd tag plus per-arch zip digests, tag pushes read the committed
- `scripts/release/winuxcmd-pin.json`, and the wpm pre-install manifest
- pins every package's artifact SHA-256 per arch (pin == index ==
- shipped bytes; a pin failure keeps the loud fail-open warn-and-ship
- path).
- Plugin verbs can no longer hang forever on a dead origin, and a failed
- add no longer strands its spec declaration (niubash#293, niubash#294,
- niubash#295, niubash#296). Every git transport a plugin verb spawns —
- `niu plugin add`/`sync`/`update`/`restore`/`distro import` — now runs
- under a 120 s wall-clock budget (`--timeout <seconds>` on `add`/`sync`,
- or `NIU_PLUGIN_FETCH_TIMEOUT_MS`, overrides it per run; the rc
- `--bootstrap` form keeps its tighter 3 s): at the budget the clone's
- whole process tree is killed (no more orphan `git.exe` helpers after a
- hand-killed niu) and the failure row names the timed-out origin. The
- spawned git also carries the user's proxy environment explicitly
- (`HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/`NO_PROXY`, each mirrored to
- its case-variant, so an uppercase-only environment still reaches git's
- case-sensitive transport). A sync stays per-source isolated: one
- stalled origin costs its own budget and a failed row while the rest of
- the spec reconciles. A declaration whose install just failed joins the
- stranded population, so `niu plugin sync --prune` removes it in the
- same run — and `niu plugin add` of a dead origin rolls its declaration
- back on the bounded failure, so the target can be re-added instead of
- deadlocking on "already declared".
- The Windows paste-chunk detection no longer swallows the first Enter of
- every line (niubash#284, a niubash#252 regression): whether a bare Enter
- submits or opens a paste chunk is now decided structurally — by the LF
- partner (`Enter + CONTROL`) queued right behind it, the console's shape
- for a machine-fed CRLF line break — not by the 50 ms arrival-burst
- heuristic. "Text + Enter" in one burst (fast human typing, ConPTY test
- drivers, expect scripts, `send-keys`) submits on the first Enter again;
- multi-line pastes keep buffering quietly until the user's own Enter
- (0 mid-paste executions, niubash#202 contract unchanged). A typeahead
- line queued behind a submitting Enter is pushed back and lands, in
- order, at the next prompt. The TypeaheadGuard no longer double-injects
- swept console input records: the sweeper's self-heal path drains the
- buffer before writing it back, so a record can no longer be reinjected
- twice (once by the self-heal, once by disarm; niubash#287).
- A paste burst's trailing bare Enter now submits the buffered chunk
- (niubash#308): a paste followed immediately by Enter — one write, the
- Enter inside the burst window — used to be swallowed, and a second
- Enter was needed. The same LF-partner lookahead that decides the
- chunk-open Enter now runs in the pending branch: silence (or any
- other queued key) behind the CR proves the chunk complete, and the
- chunk flushes and submits in one batch on the first Enter.
- Execution-level paste regressions pin empty-line fidelity across
- every shape (niubash#291, PR #317).
- The plugin trust gate now runs at runtime, not only at trust time
- (niubash#297): `niu plugin sync` and `plugin list` re-verify every
- trusted source against its lockfile pin, so a tree modified after
- `trust` reads `drift` (never a lying `unchanged`) and
- `degraded=true` with the drift reason in `--json` — the tree stops
- contributing assets until it is repaired. sync records drifted ids in
- `sources/drifted-sources.txt`, and the managed rc blocks carry a static
- guard that prints the warning at every startup (the rc stays static
- text; no hashing in it). `plugin restore` wording is honest about
- rebuilding from the pin ("rebuilt from the lockfile pin", not "tree
- matches"). bash-it's `enabled/` entries — the one legitimate tree
- mutation `niu plugin enable` itself makes — are transparent to the pin,
- while an injected `enabled/` file with unpinned content still trips
- both `verify` and `trust`.
- `niu plugin mirror set http://…` is refused without an explicit
- `--allow-plaintext` confirm (niubash#298): a plaintext mirror ships
- every git fetch unencrypted and unauthenticated, and the confirm still
- prints the standing warning. The first fetch of an unpinned non-catalog
- origin is now flagged for what it is: `plugin add` prints a
- "NOT integrity-verified" warning and the record carries
- `first_fetch_verified: false` (visible in `plugin list --json`); a
- `--checksum` pin or a curated catalog origin marks it verified.
- `niu plugin clean` reports every orphan directory under `sources/` the
- registry does not claim (niubash#299) instead of silently keeping
- manager-shaped ones and ignoring the rest; `--orphans` is the explicit
- confirm that deletes them.
- `niu plugin add <path>` positional form is first-class (niubash#300):
- a bare local directory snapshot-copies in, a single `.sh`/`.bash`
- file becomes its own one-file wild source, a nonexistent path fails
- honestly (instead of a baffling `git clone` status 128 after the
- trust-boundary banner), and the help documents the path and
- `file://` forms.
- Managed rc-block rewrites converge and self-repair (niubash#324,
- PR #325): an rc already carrying "user lines moved out" debris (the
- issue's six-section fixture — five dangling `if`s, a syntax error on
- every startup) stayed corrupt through every sync/enable/disable
- forever, because the rewrite paths never inspected the user area.
- Every plugin verb now normalizes the migrated sections first: a
- section whose every non-comment line is guard-shaped goes entirely, a
- section mixing genuine user lines with debris is kept verbatim
- (whole-section edits only — niubash#176 keeps user lines
- sacrosanct), and a re-fed user line merges into the pile instead of
- appending a duplicate section per verb. Even the startup
- `niu plugin sync --bootstrap` repairs the file — no one-shot cleanup
- command needed.
- Completion covers the commands people actually type (niubash#305,
- #306, #307, PR #319): git, tar, curl, ssh, docker, kubectl and npm
- are not WinuxCmd applets and completed as bare filenames — a second
- compiled base layer now ships hand-curated static subcommand/flag
- tables for them (no external completion script is ever executed).
- `NIU_COMPDEFS` lines that resolve to an existing directory now
- declare completion-definition directories (`<cmd>.toml` on the
- embedded applet schema, `<cmd>.bash` statically parsed by the
- bash-import reader — file bodies are never executed); previously the
- declared surface had no user-reachable entry point and zero effect in
- every mode. And `--completion-probe` sources `NIU_ENV` (`BASH_ENV`
- fallback) before the interactive rc, so compspecs declared in an
- agent init file reach the probe.
- `niu -s` reads stdin scripts on demand (rubash#586 host half,
- PR #314): the whole stdin used to be slurped with `read_to_string`
- before execution, so a `$(exec cat)`/`read`/top-level `exec cat`
- consumer hit EOF immediately and the unconsumed tail came back as
- code. The host now drives the engine's on-demand reader (the rubash
- #593 library surface): the consumer sees exactly the bytes after the
- last submitted command, and the repro is byte-verified against WSL
- GNU bash 5.3.0.
- The bash/rbash invocation faces report their name (niubash#321,
- PR #323): `bash -c 'echo $0'` printed `niu` — the shim's
- `NIU_INVOKED_AS` forward now carries all four faces
- (sh/ash/bash/rbash, login marker and `.exe` stripping preserved) and
- POSIX mode stays keyed off the face; the d3 setup-rerun journey
- answers the new TAB-completion wizard step (niubash#322).
- The `niu plugin` help and doc examples no longer point at a dead repo:
- the wild-GitHub-plugin example was `niu plugin add
- rcrowley/bash-preexec`, but rcrowley/bash-preexec 404s (the project
- moved to rcaloras/bash-preexec years ago), so a copy-paste of the
- example failed. Every live reference — help text, the plugins.toml spec
- example, the plugins guide, the wild-source fixture — now names
- rcaloras/bash-preexec, and the example repo was verified cloneable
- (niubash#301).
- The config subsystem honors `HOME` isolation (niubash#302): every niu
- subsystem — config.toml, mirrors.toml, the plugin rc, doctor, prompt,
- the setup wizard — resolves the home directory through one shared
- resolver in which an explicit `HOME` environment override wins on every
- platform. Before, the Windows resolver ranked `USERPROFILE` first, so a
- HOME-only sandbox (tests, CI, portable setups) silently read and wrote
- the real profile. A default Windows session (`HOME` unset) resolves
- exactly where it always has, and the classic Unix `HOME`-first
- convention is unchanged.
- The freshly-rendered mirrors.toml template no longer recommends
- hub.fastgit.xyz (dead since ~2021; niubash#303). The second community
- example is now ghproxy.net, verified live at edit time with a real
- `git clone` through the `insteadOf` rewrite (gitclone.com, the first
- example, re-verified the same way). The examples stay comments marked
- UNSUPPORTED — they may die at any moment and are the user's to verify.
- `.niubashrc` rewrites are atomic (niubash#304): every managed-block
- rewrite (`niu plugin enable/disable/sync`) and the setup wizard's rc
- write now stage the new content in a same-directory temp file and swap
- it in with a single replace-rename (`MoveFileExW
- MOVEFILE_REPLACE_EXISTING` on Windows, `rename(2)` elsewhere). The
- former truncate-in-place write and the wizard's former
- remove-then-rename left windows where a concurrent reader — a pipe or
- `sed` run against the rc — saw the file empty, partial, or briefly
- absent; a reader now always observes a complete old or new generation.
- The installer cleans up after itself (niubash#313). Uninstall now also
- removes the `{app}\winuxcmd\bin` entry from the user `PATH` — the
- install-time `wpm links rebuild` adds it, but the uninstaller only ever
- removed `{app}`, leaving a dangling PATH entry after every uninstall.
- The installer gained `[InstallDelete]`/`[UninstallDelete]` sweeps for
- files earlier builds shipped and the current payload retired (the
- reverted dual-name build's `niubash.exe`, stray self-update
- `niu.exe.old-*` backups), which previously survived upgrade AND
- uninstall on real machines.
- A closed stdout pipe terminates niu (niubash#245, PR #256): when the
- shell's own stdout is the pipe whose reader went away, non-interactive
- executions interpose a kernel pipe + pump thread and exit with one
- diagnostic and status 1 — previously, with no SIGPIPE on Windows,
- every later write failed again and an unbounded builtin loop printed
- one diagnostic per iteration forever (`niu -c 'yes' | head -c 1`
- never exited). The contract is pinned across every non-interactive
- execution shape — script-file producer, stdin-script producer,
- external child as the producer, and stderr gone too (niubash#286,
- PR #289).
- The history provider writes the final list back to `HISTFILE` at
- session end (niubash#265) — the missing exit half of the
- niubash#182/#250 GNU `-c` semantics: after a one-shot
- `niu -c "history -c"` the host file holds the empty list, like GNU
- bash's `exit_shell()` write. The rc-0 one-shot path returns from main
- instead of `process::exit` so the write runs.
- `sh -c` enters POSIX mode with `$0 = sh` (niubash#262): the installed
- `sh.exe` is the same forwarder binary as `bash.exe`, so Windows
- argv[0] could not carry the invocation contract to niu. The shim
- forwards its argv[0] stem as `NIU_INVOKED_AS` (actively scrubbed
- under every other name, so a nested `sh -c 'niu ...'` keeps its own
- face).
- Path-form expectations align with the engine's POSIX persona
- (niubash#283): the host cwd sync no longer re-stamps the executor PWD
- into native drive form, so the rendered form no longer depends on
- which route executed the command (simple sequences vs compound lines
- and command substitutions).
- Plain-text `--help` and a non-tty-safe banner (niubash#229), plus a warning
- when a hand-written rc would clobber the wizard theme.
- PROMPT_COMMAND exit jump now ends the session instead of returning to the
- prompt (niubash#232).
- PS0 expansion writes to stderr, matching GNU bash (eval.c:176) (niubash#233).
- Bracketed paste enabled with GNU bash multiline-paste semantics: pasted
- newlines no longer execute mid-paste (niubash#234).
- The bash-it theme history interlock (niubash#182) is closed: themes
- running `history -a && history -c && history -r` from PROMPT_COMMAND
- doubled the history file every prompt and wedged the session. `history
- -a` appends only what the file lacks (bashhist.c), `history -r` rebuilds
- the reader from disk without re-appending reedline's unsynced buffer,
- and `history -c` clears the in-memory list only — the backing file is
- snapshotted and restored instead of deleted
- (builtins/history.def:185). A ConPTY regression drives 12 prompt cycles
- of the exact theme pattern and asserts every command lands in the file
- exactly once (niubash#250).
- An unparsable plugin source registry is never silently rewritten
- (niubash#178): registry.toml bytes that fail to parse are snapshotted to
- `registry.toml.corrupt` and warned on stderr, and every registry write
- is refused — sync/install fail loudly instead of dropping the persisted
- pin commit, checksum and trust state riding in the file; install refuses
- before any fetch, and `niu doctor` names the parse error's line and the
- sidecar instead of "none installed" (niubash#246).
- Multiline paste on the Windows console no longer executes line-by-line
- (niubash#202): Windows gets no bracketed paste (ConPTY swallows the
- ?2004 markers), so a terminal paste arrived as per-line key events that
- executed as they came — a 200-line paste self-executed 186 times
- (176 KB of output, 15-17 s). A PSReadLine-style arrival-burst heuristic
- (`PasteChunkEditMode`, 50 ms inter-key gap, cfg(windows) only — Unix
- keeps the real bracketed paste above) buffers the burst, including bare
- Enters and the console's CRLF Enter pairing, and commits it as one
- insertion; the script executes exactly once on the user's Enter
- (paste-phase executions 186 -> 0; the issue's 257-line payload drops
- from >300 s timeout to ~4.6 s) (niubash#252).
- The setup wizard audit P2 batch (niubash#179): a fresh install journals
- rc_created so the finish screen's undo receipt covers the generated rc
- itself; duplicate undo receipts for one source dedupe; Ctrl-C at the
- niu-git question prints the same cancelled note as every other question;
- `niu plugin discover` enumerates the theme layer of trusted sources; zh
- translation keys realign with the runtime literals (now guarded by the
- vocab test); a gutted trusted source makes `niu plugin list`/`discover`
- name the repair verb (`niu plugin source verify <id>`) instead of
- silently showing no assets (niubash#239).
- `niu doctor` on Linux/macOS reports the Windows-only checks (winuxcmd
- core, command links, bash/sh shims) as n/a rows outside the critical
- tally instead of "1/3 critical checks passed"; the header names the
- platform and the terminal row falls back to `TERM` (niubash#194)
- (niubash#242).
- A raw ESC byte as the first character inside single quotes in a script
- file is preserved (niubash#200): the word-initial data ESC was claimed
- by the lexer's QUOTED_WORD_PREFIX marker and silently dropped; the
- engine lexer now raw-byte tags it (rubash#474) and integration tests
- pin the byte at the niu boundary across single/double-quoted, unquoted,
- ANSI-C and piped-stdin spellings (niubash#235).
Engine (rubash 1.4.0)
- Login shells: `-l`/`--login` (and an argv[0] starting with `-`) run the
- GNU startup chain on Unix — `/etc/profile`, then the first of
- `~/.bash_profile` | `~/.bash_login` | `~/.profile`; non-login
- interactive shells source `/etc/bash.bashrc` + `~/.bashrc`;
- `--noprofile`/`--norc` suppress the corresponding branch. Windows
- accepts the flags with unchanged behavior (no login concept).
- A bare-console `rubash` gets real readline line editing: a raw-mode
- console reader translates keystrokes through the engine's editor
- dispatcher — history recall (arrows, C-p/C-n), in-line cursor motion,
- C-r reverse search, per-keystroke redraw — and restores cooked mode for
- child processes.
- Command-substitution exit status is published to `$?` during expansion,
- like GNU: `echo "$(cd nope && pwd)" $?` reports 1,
- `false; echo "$(true) $?"` reports 0 (rubash#485).
- Compatibility families zeroed against the GNU 5.3.0 true-baseline
- suites: dbg-support (635→0), restricted rbash (194→0), invocation
- (BASH_ARGV0, login argv0, long-option table — 14→0), trap, function
- definition (POSIX funcname rules, AST printer), multi-operand
- `complete`; the 83-suite diff drops 3427 → 2072 lines (−40%), 32
- suites now byte-equal.
- History: `history -d start-end` range deletion (a GNU 5.3 feature), fc
- -s re-execution semantics; globstar `` fixes (non-adjacent patterns,
- zero-depth trailing slashes); array/assoc compound-assignment quoting
- fixes; same-line `#` comments after `f() {`; unquoted variable command
- words split by IFS in pipelines; `eval` inside command substitution
- re-parses like GNU; `set -u` arithmetic no longer flags assigned
- variables as unbound.
- Signal numbers unified on the Linux table (USR1=10, CHLD=17, RTMIN=34)
- so `kill -l`/`trap -l` match GNU 5.3.0; aarch64/armv7 Android compile
- against bionic; SIGPIPE no longer aborts the interpreter (`yes | head`
- streams like GNU).
- Utilities: GNU cp port (-r/-n/-i/-v/-p/-u/-t with buffered stderr),
- /bin/echo and /usr/bin/echo route to the buffered builtin channel;
- printf/ANSI-C `\u` partial-read and hex-width rules; locale subsystem
- (setlocale warnings, MB_STRLEN).
-
rubash 1.4.0 2026-10-10
新增
- 登录 shell 启动链(#487,PR #489):argv[0] 首字符 `-` 或 `-l`/`--login`(含 `-lc` 组合形态)置 login 标志后,Unix(`not(windows)` 门,覆盖 Linux 与 macOS)上登录 shell 依次 source /etc/profile → 首个存在的 ~/.bash_profile | ~/.bash_login | ~/.profile;非登录交互 shell 依次 source /etc/bash.bashrc(存在时)+ ~/.bashrc(--rcfile/--init-file 只替换个人 rc);启动文件中 `exit` 终止 shell 且后续不执行(与 --rcfile 一致)。`--noprofile`/`--norc` 分别抑制对应分支。Windows 无真实登录概念:-l/--login 接受且行为与现状完全一致(不读任何链文件)。链顺序/fallback 以注入式存在性检查做单元测试;Linux/macOS 端到端由 CI 对应 job 验证。
- 真 readline REPL(#419 readline 腿,PR #482):裸 `rubash.exe` 真控制台交互补齐 readline 行编辑——此前控制台分支 stdin 留在 cooked 行输入模式,编辑由 conhost 代管,shell 历史(↑/↓、C-p/C-n)、行内光标移动(C-a/C-e/←/→)与 C-r 反向搜索全部失效。新增 `RawConsole`(`src/console_readline.rs`):提示符与接受行之间切 raw 模式,按键逐事件翻译成引擎编辑分派器已有的字符流,编辑行每键重绘(raw 模式下终端不再回显);命令执行前恢复 cooked 模式,子进程仍见正常行纪律控制台。编辑分派器从读循环抽出为 `apply_edit_char`,cooked 字节流路径(#434 桥接语义不变)与 raw 控制台路径共享同一套绑定;PS1/PS2(含 `--rcfile`)渲染共用。补全(Tab)与 i-search 的 raw 态渲染为后续分层。
- `--version` 构建元数据(#488,PR #490):GNU license 块(保持逐字节不变)之后追加 `build: <git-hash> (<profile>)`——build.rs 编译期捕获 `git rev-parse --short HEAD`(git 不可用时回退 unknown,发布打包可 `RUBASH_BUILD_HASH` 钉值),profile 为 debug/release;Windows 附加一行 persona 说明:原生 Windows 构建、无 MSYS runtime,`*-pc-msys` MACHTYPE 不应读作 MSYS2/Cygwin bash 移植。
- GNU `cp` 移植:-r/-n/-i/-v/-p/-u/-t、递归、缓冲 stderr;`/bin/echo` 与 `/usr/bin/echo` 路由到缓冲 builtin 通道。
- `history -d start-end` 范围删除(GNU 5.3 特性)。
- `stty` builtin (PR #567): console mode queries and mutations without leaving the shell (`stty size`, `stty raw -echo`, ...), complementing the `read` fd-semantics work below.
- `bash --help` / `sh --help` fork to the GNU usage contract (#585, PR #596): the help screen follows the invocation name and prints GNU's usage contract — option table and exit status — instead of a product-generic help screen.
性能
- 性能三连(#437/#281/#242)与 bats-core 热点(#375)推进:
- #281 增量 reader 收官(PR #486,closes #281):续行扫描 admission-gate + 物理行二分戳、零拷贝参数体视图、逐行 batch-tokenizer 停靠(GNU read_token 流式模型),以及 fold 完成趟的 opener-state checkpoint——fold 趟只需支付组自身文本,不再从字节 0 重新 lex 整个累积缓冲(nvm 形状 4281 次全量趟中 19 次大重扫的 270 KB 归零)。
- #437 热点:算术循环 per-command boundary O(1) 化(数组追加与算术循环 micro-benchmark 报告的执行热点)。
- #242 执行器热路径(部分):named-event catch_flag 信号轮询、单遍 `${}` 算子索引 + 直接 simple-glob 匹配 + 算术快照容量、walker `${}` 臂 inline fragment 解析与模式快路径、allocation-free shopt 咨询、字面词快路径、comsub-scan O(n²) 修复等。
- #375 第一刀(部分):`$(cd X && pwd)` 命令替换路径解析单句柄化——一次 CreateFileW 同时取目录属性 + 物理路径(PR #484);`$(cd && pwd)×5000` 878→779 ms(−11%),bats_debug_trap 形状 −8%,输出逐位等价。
- perf-suite:专用基准 harness 与首个基线(24 probes,rubash vs WSL GNU 5.3.0)。
- Lexer construction wins(rubash#241,PR #554): fold-before-push moves, gated leading_ws capture, and inline TokStr strings — parse throughput up under alias-correct ground truth.
- Executor dispatch floor(rubash#375,PR #562): shape-only cd/pwd command-substitution admission and a DEBUG-trap parse-once cache retire the handle-table scale myth.
- Hot-path construction-only wins(rubash#242,PR #501): test argv zero-copy, gated fd marks, single word materialization, copy-on-write IFS strip, cached debug switches.
修复
- 命令替换的退出状态在展开期内即刻成为 `$?`(#485,PR #491/#494):GNU 在父壳回收替换子进程时就把其状态写入 last_command_exit_value,同一命令行内后置的 `$?`(后续词、同词后缀片段、同一简单命令的后续赋值 RHS)都看得见,成功的替换同样覆盖(`echo "$(cd nope && pwd)" $?` 报 1、`false; echo "$(true) $?"` 报 0、`v="$(exit 3)$?"` 得 v=3 且 rc=3),命令自身执行后照常覆盖。此前 rubash 只把状态记进 `last_command_substitution_status` 单元、仅在空词/赋值收尾时提升,词展开路径上丢失该时序。修复:AST/function 替换与 `&self` 包装器三个回落点的父侧回收处直接写 `exit_code`(fork 模型保存/恢复中的 `saved_exit_code` 不再回退状态),整词引号 `"$( )"` 快路径发布同一状态;反引号嵌词从遗留 `&self` 展开器改道可变嵌入 walker(与 `$()` 词共用生产路径与同一批提升点)。`local` 屏蔽赋值状态、`set -e` 下替换赋值失败中止(rc=1)而词内替换失败不中止(echo 自身 rc=0)均保持 GNU 一致。
- 函数定义与花括号组的同行 `#` 尾注释不再解析失败(#118):`f() { # note`、`f() { echo x; } # note`、`{ echo x; } # note`、`function f { … } # note`、TAB/空注释等形态此前被 lexer 把注释切进花括号组 token(`{ # note`),parser 因 `function_command.rs` 的 `value.trim() == "{"` 判定失败而回落成 `syntax error near unexpected token `('`。`skip_brace` 现在汇报配对 `}` 是否命中以及组内顶层注释起点,scanner 只把注释之前的文本作为 token 并把位置回退到注释处;`brace_close_can_end_compact_group` 也把 `}` 之后的行内 `#` 视为组结束。另修正 `skip_brace` 的 `comment_start` 初值,`f() {#note` 仍按 GNU 报语法错误而非静默接受。
- `set -u` 下算术展开不再把已赋值变量判为 unbound(#67):nounset 扫描器去掉了无法识别错误 token 时的合成 "syntax error in expression" 回退,并跳过赋值左值(`for ((i=0; i<n; i++))` 的 init/update 不再报 `i` unbound)。
- `set -u` 下算术展开的 unbound 错误与普通参数展开对齐:直接上下文中终止脚本,命令替换与管道段内只终止该子上下文(GNU expr.c expr_streval 的 FORCE_EOF 语义),并消除了随后把展开文本当命令执行的 `command not found` 级联。
- 管道中未加引号变量作命令字现在按 IFS 分词(#68):`v="echo hi there"; $v | cat` 以 `echo` 为命令名、`hi there` 为参数执行,管道任意段、子 shell 与进程替换内一致。
- 命令替换内的 `eval` 恢复完整重解析语义(#69):`x=$(eval "echo hi")` 得到 `hi`;移除把 `eval ` 前缀剥掉后当普通命令执行的捷径,`$(eval "$cmd" | sort)` 等真实脚本形态正常工作。
- dbg-support 全族归零(635→0):AND-列表双触发(execute_cmd.c 无 connection 节点火点)、source-scope trap 继承(source.def:208-216)、非行首 `{` 回归普通词法、for 每迭代行号重置。
- rsh 全族归零(194→0):`set +o restricted` 静默解除修复(set 快速路径跳过 GNU 拒绝检查)、set +r 文案/退出码、BASH_CMDS 赋值守卫、hash -p、管线成员斜杠拒绝、受限只读集、argv0 rbash 自动受限。
- invocation 全族归零(14→0):BASH_ARGV0 环境导入 $0/shell_name、login-shell argv0、长选项表、-o/-O 启动期报错 prolog、--pretty-print。
- trap 归零(3→0):ERR action $LINENO 绑失败命令行、SIGCHLD 通知排队重放、后台子进程剥离继承 trap 表、traced 函数 DEBUG 行号用 body_open_line。
- func 全族归零(58→0):POSIX funcname 规则、AST printer 移植、special-builtin 优先级。
- complete 全族归零(115→0):多操作数 compspec 注册(`complete -F f c1 c2` 双双生效)。
- history 改善(190→127):HISTIGNORE harness 修复、fc -s 语义对齐、命令替换内 fc/session 历史路由(`history -d start-end` 范围删除见新增)。
- globstar 改善(182→101):非相邻多个 `` 重复发射修复、相邻 `` 折叠后零深度目录尾斜杠。
- array/assoc 改善(444+358→246+242):复合赋值引号分组四层修复(解析器 RAW 合并、declare 操作数收集器、Word 臂原子复合、执行器逐字守卫)、元素赋值词边界、嵌套引号 patsub 逐元素替换。
- unicode 修复:printf 和 ANSI-C 引号中 `\u`/`\U` 部分读取保留、精确十六进制位数要求。
- locale 子系统:从 feat/locale-subsystem 合入,setlocale 警告、MB_STRLEN 长度。
- comsub/pipeline:pathname-expand 替换、管线 word list、assoc hash order 共享 join。
- assignment:全单引号 RHS 为字面数据、元素赋值词永不分词、转义引号内下标为数据。
- nameref:无值 nameref 接受有效目标赋值。
- SIGPIPE 语义:管道上游写端遭遇 broken pipe 不再中止解释器,`yes | head` 等流式管线行为与 GNU 一致(#455)。
- 输入规范化(粘贴语义):桥接层送达的交互式输入行在进入引擎前规范化 C0 控制字节(#458)——终端粘贴携带的 NUL/控制字节不再以未定义方式进入词法。
- source 边界的语法错误不再终止会话/脚本(#451,PR #481):sourced 文件的语法错误按普通失败中止剩余文本(GNU evalstring.c 语义)——交互会话报错后继续读下一条,非交互脚本继续执行后续命令;此前中止被误分类为 reader exit jump,交互 shell 在诊断后立即结束(ConPTY 下表现为 source 后首条命令永不执行),非交互脚本以 rc 2 中止。
- PS1 `\[`/`\]` 忽略标记在所有渲染路径剥离(#431,PR #480):oh-my-bash inretio / bash-it 主题形状下不再把标记渲染为字面文本或泄漏 raw `\x01`/`\x02` 字节;`${var@P}` 提示符变换同步剥离;真实 ESC 颜色序列与光标定位几何不受影响。
- 赋值前缀 + 子 shell 的 GNU 形态(审计语料 #452 集群1,PR #453)。
- `complete -F` 接受带连字符的函数名(#427,PR #454)。
- 复合体内 `]]` / `(( ))` 之后无终止符的 then/do 被接受(#460,PR #475)。
- 函数定义名含未引号 `$( )` 被接受(#462,PR #476)。
- `=~` 正则词片段永远是词数据,不再被误判为保留字(#461,PR #473)。
- 嵌套 keyword-form 函数体计入组 opener 追踪,不再提前 EOF(#465,PR #472)。
- 词附着的多行进程替换体跨行 join(#463,PR #483)。
- 词首数据 raw ESC 字节(如单引号首字符)不再被吞(niubash#200,PR #474)。
- ERR trap 不再对词/赋值展开错误触发(PR #467)。
- Unix 上进程替换 fd 作为外部命令参数传递(PR #468)。
- coproc 子进程继承父函数表快照,DEBUG trap 函数可解析(#441,PR #469)。
- 引号整词默认展开保持单一复合元素(PR #466)。
- Pipeline members now honor shell functions — `date(){ ...; }; date | cat` runs the function, `echo(){ printf ...; }; echo hi | cat` shadows the builtin: two dispatch sites classified a pipeline stage before any function lookup, where GNU resolves functions before the PATH search in every stage (#587, PR #594).
- A `PATH=` assignment prefix owns command resolution for its own command — `PATH=/opt/bin:$PATH git status` resolves `git` through the new PATH within that same simple command, including the pipeline and concurrent fast-path stages (#588, PR #594).
- `cat`/`mkdir`/`touch`/`cp`/`rm`/`rmdir` stop re-expanding already-expanded argv (P0 pair) — `v='*.txt'; cat $v` silently read the wrong file and the mkdir/touch/cp/rm/rmdir family created or removed wrong paths; the builtins no longer re-run expansion over words the shell already expanded (#511, PR #552; #555, PR #558).
- `$PWD`/`pwd` render the POSIX persona form unconditionally — the native-drive-style hatch is retired; it starved nvm's find_up loop, and native drive spelling now survives only at the Win32 spawn boundary per the path contract (#537, PR #556).
- GNU semantics waves (each batch issue-locked and WSL-verified against GNU 5.3.0):
- Wave 1 (PR #561): trap `exit` unwind, pipeline redirect rejection, nameref unbound gating, brace-expansion shape validation, redirect `set -u`, arithmetic latch (#524 #525 #529 #532 #535 #536).
- Wave 2a (PR #560): SIGQUIT ignored in non-interactive shells, `-s` argument scan order, readonly attribute direction, `declare -na` structure wins, ERR single-fire guard.
- Wave 2c (PR #573): RANDOM reseed on `declare -i`, heredoc-max fatal ordered before the unclosed-substitution scan, export/array/dynamic-variable/encoding semantics (#533 #534 #538-#543).
- Wave 3 (PR #569): xtrace quote fidelity, redirect message rendering, `caller` frame limits, ERR trap function-entry, extdebug DEBUG-skip sites, `kill` signal names (#517-#522, #550).
- Wave 4a (PR #576): redirect ambiguity gate, backtick diagnostic window, command-position paren, compgen tilde expansion (#514-#516, #523).
- Wave 4b (PR #578): `$[ ]` EOF scan, `declare -aA`, procsub fd window, trap listing/DEBUG freeze, `bash -o` listing, jobs/alias parameter semantics (#543-#547, #549).
- `read` console/fd semantics — empty-line exit status, fd 0 file-loop ownership, carrier-byte domain normalization, dup-stdin console reads, fractional `-t` deadlines, and the dup-fd ladder behave like GNU (#275-#282, PR #567).
- `-s` stdin-script reads on demand through the canonical library driver — the parser consumes stdin one command at a time like GNU's non-seekable stream, so a `$(exec cat)`/`read`/top-level `exec cat` consumer sees exactly the bytes after the last submitted command and the unconsumed tail never executes as code (the `curl | bash -s` install-script hazard). The driver moved into `script_driver::run_stdin_script`, so embedding hosts get the same contract instead of slurping (#586, PR #593). Related: an exec'd child keeps the shell's stdin channel in every context (#575, PR #583).
- CLI entry-shape semantics — POSIX special-builtin/assignment fatals map per entry shape (`-c` exits 127 like GNU `run_one_command`, scripts/stdin keep 1, subshell-contained stays 1); the shim forwards the bash/rbash invocation faces so `$0` reports the invoked name (and `sh`/`ash` keep the POSIX-mode contract) (#589 #590 #591, PR #598; #504).
- `eval` EOF-shape probe is comment-aware — restores the modernish init battery (the #318 `unclosed_comsub_eof_shape` regression) (#282, PR #572).
- Multiline `[[ ]]` with folded `(( ))` continuations parse like GNU, the `bind -v`/inputrc engine surface is honored, and the #433 rc regression ledger is pinned (PR #571).
- Arithmetic-for sections evaluate the verbatim `(( ))` capture and carrier bytes never reach eval-error echoes (#548, PR #584).
- Compound redirection failures reach the ERR trap again (the body-exemption gate was type-blind, PR #557), and the redirect expansion-error verdict propagates on the function fast path (PR #579).
- P1 semantic batch — errexit body adjudication, stdin parse-error status, printf write-failure status, dirstack `EX_USAGE` (PR #553).
- POSIX-mode coverage audit completed — 42/75 audited GNU posix-mode behaviors implemented: fatal assignment families, redirect no-split, `time` fd-2 routing, trap usage exits (PR #559).
- Diagnostic fidelity — the C-family message formats match GNU: stdin line offsets, dolbrace scanning, PST_REGEXP two-stage EOF, outermost unclosed-block line naming, write-error prefix, and the `[[ =~` two-part diagnostic (#551, PR #563); a top-level unclosed procsub continues to the next line and `[ (` is reported as a diagnostic token (#564 #565, PR #577).
- Windows path contract — POSIX drive-form operand pre-translation now covers every non-shell-wrapped child class with context/position/existence invariance: `/d/...` operands work the same inside loops, subshells, functions, pipelines and command substitution, for to-be-created targets and flag values, while data operands (grep/sed/awk patterns, `basename`/`dirname`) stay verbatim (#592, PR #600); `/mnt/c` is retired from the verbatim loop per the #568 adjudication (PR #574); bare drive-root POSIX forms are accepted (#566, PR #577).
- 回归钉子(无用户可见行为变化):sourced `exit N` 终止语义(PR #457)、函数定义花括号行 token 语义(PR #459)、#424 重定向目标拒绝与 procsub 输入侧(PR #464)。
更新
- GNU Bash 5.3.0 兼容性大幅推进。83 套件 true-baseline 总差异从 3427 行降至 2072 行(−40%),零差套件从 31 增至 32。
平台
- Android 目标:aarch64/armv7 android 目标可针对 bionic 编译(#456,`sa_restorer` 平台门修正);kill/trap 信号表在 android 上同样编译 Linux 编号的 1..31 槽位(bionic 与 Linux 内核编号逐值一致)——此前 `kill -l 17` 在 android 上输出为空、`trap -l` 列出 bionic 不用的 BSD 名;RT 字面量块保持 glibc 契约的 Linux 专属(PR #492)。
- 平台门全量审计(PR #492):src/ 下 511 处平台 cfg 盘点分类;确认无「漏 macOS 腿」同类问题,真 Linux 专属门(RT 34..64 字面量、`sa_restorer` 等)保留。
- OpenHarmony / musl:`ulimit` 针对 musl 与 OpenHarmony libc 编译修复(PR #449)。
- 信号编号统一:rubash 全表从 BSD/Cygwin 风格切换到 Linux 表(USR1=10、CHLD=17、RTMIN=34),与 GNU 5.3.0 WSL 契约一致;kill -l/trap -l 输出逐字节对齐。
- Residual Windows assumptions are gated outside `cfg(windows)` so non-Windows builds stay honest (PR #499).
文档
- 重写 README.md 和 README.zh-CN.md:数据驱动的兼容性展示、近期修复表、架构概览;其后一次事实保持的风格微调(PR #495)。
- 更新 COMPATIBILITY-STATUS.md:2026-09-11 全量 true-baseline 重跑结果、总体结论刷新。
- 归档过时文档 7 个(gnu-bash-compatibility-implementation-plan、bash-implementation-inventory、bash-source-map、performance-debugging-process、typed-expansion-migration-checkpoint、source-layout、HANDOFF-20260829)。
- README restructured again, evidence-first, with the cross-platform identity (EN+zh) (PR #500); the three boundary invariants — native core, POSIX forms, verbatim fidelity — are codified as contract tests plus a windows-path-contract document and proxy-driver guide (PR #570).
- Platform gap documented: foreign children cannot receive exec-established numbered fds (#378, PR #580).
- Baseline numbers (PERF/CORPUS/COMPAT/LINUX) extracted from archived status ledgers (#477, PR #581); the debugger M0 design (DAP-over-stdio) is published (#497, PR #582); dangling COMPATIBILITY-STATUS references are repointed to the archived ledger (PR #502); the GNU suite ledger closes at 83/83 with the nameref residual proven to be GNU's own coproc reap race (PR #506).
构建
- crates.io 打包瘦身:发布包不再携带 `tests/` 与 `third_party/`(参考源码与测试语料,仅本地开发/CI 需要),包内容 2053 个文件 / 15.6 MB → 400 个文件 / 8.9 MB,回到 crates.io 10 MiB 上限之内;`exclude` 同步移除失效的 `!third_party/bash/tests/*.right` 反规则。功能与构建不受影响(`cargo publish --dry-run` 验证通过)。
- Internal: twelve ghost module skeletons that never entered the compile tree are removed (PR #597); stale regression snapshots are re-baselined with an airtight snapshot env and a CI golden-refresh artifact (PR #496).
-
niubash 1.3.4 2026-10-05
Added
- Theme gallery live prompt preview (isolated render, 1.5s bound) in the
- wizard and `niu plugin ui` (verb now registered) - highlight moves, the
- pane below renders that theme's actual prompt.
- Collections install curated independent recipes (complete-alias,
- fzf-git.sh, bash-sensible, git-flow-completion - upstream-audited).
- 178/178 winuxcmd applet completions embedded (zero-config Tab completion
- for every bundled command; user TOMLs keep highest priority).
- The golden user journey is a required release gate: install -> wizard ->
- trust -> theme -> new terminals -> daily battery, plus persistence,
- wizard-rerun, spec hand-editing and remove-active-source phases
- (`--phases all`), all driven from the tag's own fresh build.
- Per-asset timing budgets (437 themes/plugins/completions, zero
- sampling) wired as a second release gate; budgets ratchet down only.
Fixed (engine rubash 1.3.4)
- Theme choices survive `source` and new terminals: the wizard/gallery pick
- is pinned into the spec, sync reconciles same-name claims toward the rc
- (the "activated/deactivated" revert is gone), and the defaults-as-floor
- never resurrects a framework over a claimed theme.
- Multi-line theme prompts place the editing cursor at end-of-input-line:
- the theme's right-align cursor surgery is served through the line
- editor's own right-prompt channel (escape-excluded width math).
- A 6-second first prompt after picking `full` is gone: startup fetches run
- under a hard 3s budget (kill-on-close job object, credential-prompt
- guard) and memoize; in-sync machines never fetch at all.
- `tr -d/-s/-c` in pipelines actually runs (the translate fast path had
- read `-d` as a character set); `for f in "${arr[@]:-}"` yields
- per-element words; `/usr/bin/seq`-style file operands resolve through
- `.exe` existence (directory forms unchanged); parser throughput +13%
- (nvm -n 18.0x -> 15.6x vs GNU).
Fixed (product, found by the audit lanes and the golden journey)
- add/rc audit P2 batch (niubash#176): `niu plugin add -h` is a usage
- request (was an unknown-option error); the plugin usage and the add usage
- document `--url`; a local directory named like a catalog id
- (`oh-my-bash/`) is adopted as a local tree instead of being hijacked to
- the catalog's GitHub origin; cwd-relative path targets are stored
- cwd-independent (normalized absolute, forward slashes) in the spec; the
- already-declared refusal points stranded declarations at
- `niu plugin sync --prune` instead of a remove that would fail; user lines
- hand-written inside a managed rc block are migrated out with a warning
- instead of being silently dropped; a CRLF rc keeps its line endings
- through block rewrites; hand-edited near-marker blocks are repaired
- instead of duplicated; every plugin verb that rewrites the rc backs it up
- first and the backup set is pruned to the 10 newest.
- `plugin add --checksum` is honored through add and adopt; failed adds
- roll back their spec entry and legacy strands are pruned; enable/disable
- surface rc-write failures and collapse duplicate managed blocks.
- The wizard rc's bootstrap line references the running binary
- (`${NIU_SHELL:-niu}`), so a stale niu on PATH can no longer hijack it;
- doctor warns when the PATH-resolved niu differs from the running exe.
- `--help`/usage/docs truth pass (ui/recipe/distro/mirror/source/rollback
- verbs listed; sync --adopt documented; WinHttp 12175 gets a reason).
-
niubash 1.3.3 2026-10-04
Fixed (engine rubash 1.3.3)
- Keystrokes typed while the prompt rebuilds are no longer eaten: external
- children spawned by PROMPT_COMMAND (the theme clock's date/git/awk probes)
- used to consume the first pending console input record - a human typing
- right after a prompt refresh lost their first key (`echo` -> `cho`). A
- typeahead guard now sweeps pending input during prompt machinery and
- reinjects it verbatim before the editor reads.
- `tr` with options in a pipeline actually runs (`tr -d b` was silently
- passed through - the translate fast path had read `-d` as a character
- set).
- `for f in "${arr[@]:-}"` yields per-element words like GNU (bash-it
- theme loading works).
- Parser throughput: nvm -n ~13% faster (backslash-continuation scan
- admission gate + binary-search line stamping), GNU ratio 18.0x -> 15.6x.
-
niubash 1.3.2 2026-10-04
Engine fixes
- Alias expansion no longer leaks into compound array assignments OR
- `[[ ]]` conditional / extglob pattern words - the oh-my-bash +
- bash-completion interactive corruption family is closed (the
- `bash_completion: line 1376` syntax error).
- Single-quoted words keep their integrity through pipeline and
- command-substitution stages (three stacked root causes fixed).
- `cat` applies all formatting options when reading a pipe (`cat -n`
- in a pipeline numbers lines).
- `cd` reaches external children (interactive reader no longer restores
- the process cwd per line).
- `-c` subshell fatal expansions exit 1 like GNU, not 127.
- `\u` renders the OS account name, env-independent.
- Virtual system-root arguments (`/usr/bin`, `/etc`, `/tmp`, ...) are
- resolved through the root map for every child class.
- Console-attached bare rubash renders the real PS1/PS2 channel instead
- of a placeholder REPL (GNU interactivity rule, error survival, exit
- codes).
First three-platform release (engine rubash 1.3.2)
- Linux (x86_64 + aarch64, glibc) and macOS (arm64 + x86_64) portable
- tarballs join the Windows packages. Every platform's build job
- smoke-runs the binary it ships, on that OS, before any artifact is
- attached. Windows asset names and the self-update link logic are
- unchanged.
Product fixes (found by the new golden user journey gate)
- Same-name themes (powerline-multiline exists in both frameworks) now
- route to the owning framework's native loader and actually apply.
- The curated bash-preexec recipe entry matches the real upstream file;
- all curated entries are pinned against upstream roots.
- A failed collection apply journals and reports the failure honestly
- with the retry command.
- The golden user journey (fresh install -> wizard -> trust -> theme ->
- new terminals -> daily battery) now runs as a required release gate.
-
niubash 1.3.1 2026-10-03
Fixed
- Interactive arithmetic corruption (engine rubash 1.3.1): aliases no
- longer expand inside compound array assignments. Under oh-my-bash, the
- convenience alias `1='cd -'` leaked into the literal
- `OMB_VERSINFO=(1 0 0 0 ...)` and corrupted it to
- `(_omb_directories_cd - 0 ...)`, killing version arithmetic with
- `-: arithmetic syntax error` in interactive sessions (any theme,
- powerbash10k included). GNU parity: assignment words are never
- alias-expanded (parse.y parse_compound_assignment keeps the whole
- `( ... )` in one ASSIGNMENT_WORD token).
- Plugin sync state machine: the startup "installed but not declared"
- nag now prints only when a spec exists (imperative mode is silent);
- `niu plugin sync --adopt` declares installed sources with a
- round-trip-stable snapshot (enablement + theme); the setup wizard ends
- spec-managed; `niu plugin add` adopts an already-installed source
- instead of erroring "remove it first".
- No re-download loop: the startup bootstrap memoizes failed installs
- (per origin+ref ledger); retries happen only on an explicit
- `niu plugin sync`. The bash-it fingerprint now matches the real
- upstream layout (its `lib/composure.bash` was removed upstream).
- `niu plugin source remove` also drops the spec declaration (no
- resurrection), and duplicate spec declarations of one source merge
- instead of flipping the rc block every sync.
-
rubash 0.3.0 2026-08-22
修复
- 修正关联数组算术下标的引号处理;带引号的 `$()` 和算术下标不再被错误地预展开。
- 改进数组元素下标诊断,对空、`*` 和负下标报告与 Bash 一致的错误,同时保留参数展开的非致命行为。
- 修正参数展开扫描器对单引号内 `}` 的处理,并拒绝 Bash 不支持的空格包围命令替换形式。
- 修正算术条件错误 token 的渲染,以及 `enable -d` 无效 builtin 的使用状态和退出码。
- 保留进程替换、coproc、printf 溢出和 extglob 解析等近期修复的回归覆盖。
- alias 展开为 `if ...; then`、`while ...; do`、`for ...; do` 等复合语句前缀时,会继续拼接到匹配的 `fi`/`done` 后重解析执行,补齐更接近 Bash 的 parser-level alias 行为。
- 赋值 RHS 紧邻 `<(...)` 进程替换时会作为赋值值解析并物化为可读路径,补齐 `p=<(cmd); cat "$p"` 这类 Bash 语法框架。
- 赋值 RHS 中的 `>(...)` 输出进程替换会登记为可写路径,并在后续重定向写入该路径后执行替换命令。
- `[[ -e <(...) ]]`、`[[ -p >(...) ]]` 等条件文件测试会识别进程替换操作数的 pipe/fd 形状,补齐条件命令中的进程替换文件测试框架。
- `for`/`select` 词列表中的 `<(...)`/`>(...)` 会先物化为可读/可写路径,再作为循环变量值使用。
- `case` 匹配词中的 `<(...)`/`>(...)` 会先物化为路径,再参与 pattern 匹配。
- 同一 word 中混合 `$((...))`/`$[...]` 算术展开和参数展开时,按 Bash 的从左到右顺序保留自增等副作用。
- `test -v` 与 `[[ -v ... ]]` 会识别 `RANDOM`、`SECONDS`、`BASH_COMMAND` 等动态 Bash 参数的已设置状态。
- Windows/Git Bash 环境下 `test -O`、`test -G` 与 `[[ -O/-G path ]]` 对当前用户创建的现有路径返回成功,补齐所有权条件 unary 的跨平台框架。
- `case` pattern 中被引号保护的 `*`、`?`、`[` 等 glob 元字符会按字面量匹配,变量在双引号 pattern 中展开后也保持字面匹配语义。
- 关联数组 key 含 `]` 时会在内部存储和 `declare -p` 输出中强制加引号,避免 `declare -A a=([x] one)` 这类交替 key/value 语法把字面 key `"[x]"` 误解析为下标 `x`。
- alias 引入复合语句前缀时,匹配 `fi`/`done`/`esac` 会按嵌套复合语句深度处理,避免内层 `if` 或循环提前截断外层 alias 语句。
- alias 引入 `time` 前缀并计时复合命令时,同样按嵌套深度收集被计时的 `if`/循环主体,避免内层结束词提前截断 `time` 语句。
- alias 引入 `case` 时,case clause body 边界会跳过内层 `case ... esac`,避免内层 `;;`/`esac` 提前截断外层 clause。
- alias 值提供 `function name` 前缀、函数体由后续 `if` 或循环复合命令提供时,会继续收集到匹配结束词后重解析为函数定义。
- 输出方向进程替换作为普通参数传给管道外部命令阶段或函数调用时,会物化为可写路径,并在阶段/函数结束后执行替换命令。
- 管道中的复合命令和函数阶段改为子 shell 风格执行,避免 `cd`、函数定义和算术赋值等状态泄漏到外层 shell。
- `builtin` 命令统一走完整内建分发表,避免无重定向调用时漏掉 `let`、`read`、`mapfile`、job control 等已接入内建命令。
- `shopt -s lastpipe` 开启后,管道最后一段可在当前 shell 执行,使 `read` 和 `while read` 等 final stage 状态更新保留到外层。
- `coproc` 默认管道的父子端接线修正,并将 coproc 子进程纳入 `wait` 可识别的作业表,避免 brace body 在 Windows 上出现 stdio 访问错误。
- 反引号命令替换外部的转义反引号会保留为字面量,反引号命令替换内部的转义反引号分隔符可用于嵌套命令替换。
- 命令替换内部的简单 word splitting 会将嵌套 `$()` 保持为同一个 word,避免 `$(echo $(echo nested))` 被内部空格拆碎。
- `printf -v` 支持将格式化结果写入 indexed array 和 associative array 的元素目标。
- `printf -v arr[subscript]` 的 indexed array 目标会按 Bash 算术规则解析 subscript,并支持已有数组的负下标。
- `compgen -W` 支持从 wordlist 生成匹配候选,并接入 `-P`/`-S` 前后缀和无匹配返回状态。
- `compgen -V varname` 支持将生成的候选写入 indexed array 变量而不是输出到 stdout。
- `compgen -A builtin` 和 `compgen -A keyword` 会生成内建命令/保留字候选,并支持 prefix 过滤和 `-P`/`-S` 包装。
- `compgen -b` 和 `compgen -k` 会分别按 Bash 的短 action 形式生成内建命令与保留字候选。
- `compgen -A signal` 会复用当前 `trap`/`kill` 支持的信号名称表生成候选。
- `compgen -A shopt` 和 `compgen -A setopt` 会复用当前支持的 `shopt` / `set -o` 名称表生成候选。
- `compgen -G` 会展开 glob pattern 生成路径候选,并支持 `-P`/`-S` 包装和无匹配状态。
- `compgen -X` 会过滤已生成候选,支持 `!pattern` 反向过滤,并保留 Bash 的候选生成状态语义。
- `compgen -d` 和 `compgen -A directory` 会从文件系统生成目录候选,并支持 prefix 过滤、`-P`/`-S` 包装和 `-X` 过滤。
- `compgen -f` 和 `compgen -A file` 会从文件系统生成文件名候选,包含普通文件和目录,并支持现有候选过滤与包装流程。
- `compgen -A helptopic` 会复用 `help` 主题表生成帮助主题候选。
- `compgen -A enabled` 会生成当前支持且未被禁用的启用内建命令候选。
- `compgen -A disabled` 会基于 `enable -n` 状态生成禁用内建命令候选。
- `compgen -j` 和 `compgen -A job` 会基于当前后台 job 表生成 job 命令候选。
- `compgen -A running` 会基于当前后台 job 表生成运行中 job 命令候选。
- `compgen -A stopped` 接入停止状态 job 候选框架,当前无 stopped job 状态时保持空成功。
- `compgen -A hostname` 会基于当前 shell 的 `HOSTNAME`/`COMPUTERNAME` 生成主机名候选。
- `compgen -A binding` 会基于 GNU Readline 默认函数名表生成 binding 候选。
- `compgen -u` 和 `compgen -A user` 会基于当前 shell 的 `USER`/`LOGNAME`/`USERNAME` 生成用户候选。
- `compgen -g` 和 `compgen -A group` 会基于当前 shell 的 `GROUP`/`GROUPNAME`/`USERDOMAIN` 生成用户组候选。
- `compgen -s` 和 `compgen -A service` 会基于当前 shell 的 `SERVICE`/`SERVICENAME` 服务名状态生成服务候选。
- `compgen -v` 和 `compgen -A variable` 会基于当前 shell 变量表生成变量名候选。
- `compgen -a` 和 `compgen -A alias` 会基于当前 shell alias 表生成别名候选。
- `compgen -A function` 会基于当前 shell 函数表生成函数名候选。
- `compgen -c` 和 `compgen -A command` 会合并内建命令、保留字、alias、函数和 `PATH` 文件生成命令名候选。
- `compgen -A arrayvar` 会基于当前 shell 的 indexed/associative array 标记生成数组变量候选。
- `compgen -e` 和 `compgen -A export` 会基于当前 shell 的 exported 变量标记生成导出变量候选。
- `compgen -A readonly` 会基于当前 shell 的 readonly 变量标记生成只读变量候选。
更新
- 本版本将项目版本、兼容性证据和近期 Bash 语义修复同步到当前源码状态。
文档与验证
- 发布当前兼容性归因账本,区分真实 Bash 输出差异、宿主/fixture 问题和历史 runner 期望文件。
- 更新 Windows-native 路径、bashdb 调试边界及 GNU Bash 源码语义映射说明。
- 明确 `cargo` 包版本为 `0.3.0`;上游 `run-*` 的 `87/87` 仅表示旧 `.right` 期望通过,不代表真实输出已经 83/83 对齐。
构建
- CI 在推送 `vN.N.0` 发布 tag 后,会等待测试通过并自动创建 GitHub Release。
- Release workflow 支持在 Actions 页面手动输入发布 tag 触发,并会校验 tag、checkout 提交和 Cargo 版本一致。
测试
- 增加 alias 值内含复合语句控制词和分号的 `if`、`while`、`for` 回归覆盖。
- 增加赋值 RHS 中独立和嵌入式输入进程替换的回归覆盖。
- 增加赋值 RHS 中独立和嵌入式输出进程替换的回归覆盖。
- 增加 `[[ ... ]]` 条件文件测试中输入/输出进程替换操作数的回归覆盖。
- 增加 `for`/`select` 词列表中输入/输出进程替换的回归覆盖。
- 增加 `case` 匹配词中输入进程替换的回归覆盖。
- 增加同一 word 中算术展开与参数展开交错时序的回归覆盖。
- 增加 `test -v` 与 `[[ -v ... ]]` 检测动态 Bash 参数的回归覆盖。
- 将 `test -O/-G` 与 `[[ -O/-G ... ]]` 的现有路径覆盖对齐 Windows/Git Bash 行为。
- 增加 `case` 引号 pattern 与双引号变量 pattern 的字面匹配回归覆盖。
- 增加关联数组交替 key/value compound assignment 和普通元素赋值中 bracket key 的回归覆盖。
- 增加 alias 引入的外层 `if`/`for` 内嵌套 `if`/`while` 的结束词匹配回归覆盖。
- 增加 alias 引入 `time` 前缀后计时嵌套 `if` 和嵌套循环的回归覆盖。
- 增加 alias 引入 `case` 后 clause body 内嵌套 `case` 的回归覆盖。
- 增加 alias 引入 `function name` 前缀后接 `if` 和 `for` 函数体的回归覆盖。
- 增加 `tee >(cat ...)` 管道阶段和函数参数中的输出进程替换回归覆盖。
- 增加管道 brace group 与函数阶段的工作目录隔离覆盖,并将函数定义、算术命令 pipeline 阶段预期对齐 Bash。
- 增加 `lastpipe` final `read`、final `while read`、final 赋值和 `PIPESTATUS` 状态覆盖。
- 增加 `coproc NAME { ...; }` 默认 stdout pipe 与 `wait $NAME_PID` 的回归覆盖。
- 增加转义字面反引号和嵌套反引号命令替换的回归覆盖。
- 增加嵌套 `$()` 命令替换保持完整 word 的回归覆盖。
- 增加 `printf -v arr[index]` 和 `printf -v assoc[key]` 的回归覆盖。
- 增加 `printf -v` indexed array 算术下标和负下标目标的回归覆盖。
- 增加 `compgen -W` prefix 过滤、`-P`/`-S` 输出和无匹配状态的回归覆盖。
- 增加 `compgen -V varname` 数组写入和 stdout 抑制的回归覆盖。
- 增加 `compgen -A builtin` 和 `compgen -A keyword` 候选输出、过滤和无匹配状态的回归覆盖。
- 增加 `compgen -b` 和 `compgen -k` 短 action 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A signal` 信号候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A shopt` 和 `compgen -A setopt` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -G` glob 候选输出、`-P`/`-S` 包装和无匹配状态的回归覆盖。
- 增加 `compgen -X` 普通过滤、反向过滤和全过滤状态的回归覆盖。
- 增加 `compgen -d` 和 `compgen -A directory` 的目录候选、过滤与包装回归覆盖。
- 增加 `compgen -f` 和 `compgen -A file` 的文件名候选、过滤与包装回归覆盖。
- 增加 `compgen -A helptopic` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A enabled` 候选输出与无匹配状态的回归覆盖。
- 增加 `compgen -A disabled` 禁用内建命令候选和 `compgen -A enabled` 排除禁用项的回归覆盖。
- 增加 `compgen -j` 和 `compgen -A job` 后台 job 候选、过滤与包装回归覆盖。
- 增加 `compgen -A running` 运行中 job 候选、过滤与包装回归覆盖。
- 增加 `compgen -A stopped` 无停止状态 job 时空输出成功的回归覆盖。
- 增加 `compgen -A hostname` 主机名候选、过滤与包装回归覆盖。
- 增加 `compgen -A binding` readline 函数候选、过滤与包装回归覆盖。
- 增加 `compgen -u` 和 `compgen -A user` 用户候选、过滤与包装回归覆盖。
- 增加 `compgen -g` 和 `compgen -A group` 用户组候选、过滤与包装回归覆盖。
- 增加 `compgen -s` 和 `compgen -A service` 服务候选、过滤与包装回归覆盖。
- 增加 `compgen -v` 和 `compgen -A variable` 的变量候选、过滤与包装回归覆盖。
- 增加 `compgen -a` 和 `compgen -A alias` 的别名候选、过滤与包装回归覆盖。
- 增加 `compgen -A function` 的函数名候选、过滤与包装回归覆盖。
- 增加 `compgen -c` 和 `compgen -A command` 的命令候选、过滤与包装回归覆盖。
- 增加 `compgen -A arrayvar` 的数组变量候选、过滤与包装回归覆盖。
- 增加 `compgen -e` 和 `compgen -A export` 的导出变量候选、过滤与包装回归覆盖。
- 增加 `compgen -A readonly` 的只读变量候选、过滤与包装回归覆盖。
-
oh-my-niu 1.0.1 2026-08-02
Changed
- Released first-party `.winux` source packs for Git, Docker, kubectl, npm,
- zoxide, direnv, dotenv, fzf, last-working-dir, and thefuck against the
- Winuxsh source lifecycle host.
- Added the framework entry point, directory-first official plugins,
- `prompt-core`, and theme plugins for minimal, classic, pure, compact,
- cyberpunk, forest, and ocean.
- Kept keybindings and command-not-found as bridge plugins while their low-level
- Reedline/provider call sites remain host-owned.
- Kept old `packs/` manifests as compatibility and package-review metadata over
- the directory plugin surface.
- Added `tools/smoke_framework.winux` for runtime verification with an
- installed Winuxsh binary.
- Updated bundle metadata with `api = "winuxsh:[email protected]"` and
- `min_winuxsh = "0.10.0"` so older Winuxsh hosts refuse this RC-first
- source-pack bundle.
- Added deterministic release packaging for `oh-my-winuxsh-1.0.1.zip` and the
- matching `.sha256` checksum file.
Compatibility
- Requires Winuxsh 0.10.0 or newer for source plugin startup and lifecycle hook
- loading through `.winux` files.
- Keeps process and WASM plugin protocols unchanged; WASM provider/effect APIs
- remain future host work.
Release Checklist
- Attach `oh-my-winuxsh-1.0.1.zip` and
- `oh-my-winuxsh-1.0.1.zip.sha256` to the GitHub release.
- Include this changelog entry and `docs/compatibility.md` in the release notes.
- Run `python tools/validate_bundle.py` and
- `python tools/package_bundle.py --check` before publishing.
- Run `winuxsh tools/smoke_framework.winux .` before publishing framework
- changes.
-
oh-my-niu 1.0.0 2026-07-31
Added
- Established the repository as the official Winuxsh plugin bundle rather than
- a legacy `.winsh` script framework.
- Added bundle metadata with `api = "winuxsh:[email protected]"` and
- `min_winuxsh = "0.8.3"`.
- Added first-party source packs for Git, Docker, kubectl, npm, zoxide,
- direnv, dotenv, fzf, last-working-dir, and thefuck with bundle-local `.winux`
- startup and lifecycle hook code.
- Added first-party builtin/native packs for command-not-found, prompts, and
- keybindings where host-owned provider/editor/prompt behavior still applies.
- Added bundle-owned aliases, completions, prompt presets, keybinding metadata,
- official theme assets, authoring templates, and public authoring documentation.
- Added deterministic release packaging for `oh-my-winuxsh-1.0.0.zip` and the
- matching `.sha256` checksum file.
Compatibility
- Requires Winuxsh 0.8.3 or newer for local bundle install, rollback, doctor,
- review, process plugin, and Phase 8 WASM command execution support.
- Uses pack manifest API `winuxsh:[email protected]`, process plugin protocol
- `winuxsh:[email protected]`, and WASM plugin protocol
- `winuxsh:[email protected]`.
- Keeps source, process, and WASM execution visible through manifest
- permissions because they can execute outside pure static asset loading.
Release Checklist
- Attach `oh-my-winuxsh-1.0.0.zip` and
- `oh-my-winuxsh-1.0.0.zip.sha256` to the GitHub release.
- Include this changelog entry and `docs/compatibility.md` in the release notes.
- Run `python tools/validate_bundle.py` and
- `python tools/package_bundle.py --check` before publishing.
-
rubash 0.2.0 2026-07-17
新增
- 补齐复合数组赋值中的未引用命令替换拆词行为,并在拆出的字段上继续执行
- pathname expansion。
- 补齐 `arr=($var)` 这类未引用参数复合数组赋值字段的 pathname expansion。
- 复合数组赋值普通元素支持 brace expansion,并遵守 `set +B` 对 brace expansion
- 的关闭状态。
修复
- quoted parameter expansion 后不再误触发 pathname expansion,行为更接近 Bash。
- Linux/Unix 上执行无 shebang 外部脚本时,遇到 exec format error 会通过 shell
- 回退执行,行为更接近 Bash。
- Unix shell 回退不再完全依赖被脚本或测试改写过的 `PATH`,会兜底查找标准
- `/bin/sh`、`/usr/bin/sh`、`/bin/bash` 和 `/usr/bin/bash`。
文档
- 更新 README 中的当前实现状态、builtins 覆盖、测试规模和代码结构说明。
- 更新 README 中的功能进度,说明当前实现已超过早期骨架阶段,重点转向 Bash
- 兼容细节和上游测试覆盖。
- 更新 README 和 GNU Bash upstream 测试文档中的测试进度、运行命令和更新时间。
测试
- 统一测试中临时外部命令的写入逻辑,在 Unix 上自动设置可执行权限,修复
- Linux CI 中禁用 builtin 后走外部命令相关用例返回 `126` 的问题。
- GNU Bash upstream runner 本地基线更新为 `87/87` 通过。
- 将 `run-minimal` 纳入默认 upstream runner 集合。
- 收敛 upstream bridge 的重复输出逻辑,降低后续维护成本。
-
rubash 0.1.1 2024-06-11
增强执行器
- #### 已完成
- ##### 执行器
- 新增内建命令: `env`, `set`, `unset`, `test`, `[`
- 添加 `redirect_err_append` 字段支持 `2>>` 重定向
- 使用 match 语句简化代码,替代函数指针
- ##### 测试
- 扩展执行器测试: 4 → 18 个测试
- 新增环境变量测试
- 新增命令链接测试
- 新增内建命令测试
-
rubash 0.1.0 2024-06-11
已知问题
- 管道尚未实现真正的进程间通信
- 变量展开尚未实现
- 不支持控制流语句
- ---
待完成
- [ ] 变量展开 (`$VAR`, `${VAR}`)
- [ ] 控制流 (`if`, `while`, `for`, `case`)
- [ ] 管道实现 (真正的进程间通信)
- [ ] 函数定义
- [ ] 作业控制
- [ ] 命令历史
- [ ] 更多内建命令 (`read`, `printf`)
首次发布
- 这是一个重要的里程碑,完成了 Shell 的核心功能。
- #### 已完成
- ##### 词法分析器 (Lexer)
- 基础分词 (单词、符号、关键字)
- 运算符识别 (`|`, `&`, `;`, `<`, `>`)
- 引号处理 (`'`, `"`)
- 变量识别 (`$VAR`, `${VAR}`)
- 命令替换 (`` `cmd` ``, `$(cmd)`)
- 花括号展开 (`{1..5}`, `{a,b,c}`)
- 注释处理 (`#`)
- 转义字符处理 (`\`)
- ##### 解析器 (Parser)
- AST 生成
- 简单命令解析
- 管道解析
- 分号分隔命令
- 赋值语句解析
- 重定向解析
- ##### 执行器 (Executor)
- 内建命令: `exit`, `echo`, `pwd`, `cd`, `export`, `true`, `false`
- 外部命令执行
- I/O 重定向支持
- 退出码处理
- #### 测试
- 词法分析器测试: 33 个
- 解析器测试: 13 个
- 执行器测试: 4 个
- 单元测试: 8 个
- 总计: 58 个测试
- #### 重构
- 使用 Rust 2021 新特性 (`matches!`, `let...else`)
- 代码优化: 减少约 48% 行数
- 添加 `#[inline]` 优化
- #### 文档
- README.md
- CONTRIBUTING.md
- CODE_OF_CONDUCT.md
- LICENSE (GPL-3.0)
-
niu-git v2.56.0.1 日期未知
-
niu-git v2.55.0.5 日期未知
-
niu-git v2.55.0.4 日期未知
-
niu-git v2.55.0.3 日期未知
-
niu-git v2.55.0.2 日期未知
-
niubash Unreleased 未发布 日期未知
Added
- `plugins::loader`: the generated-loader module — document format (banner, superseded-marker record, per-source sections byte-identical to the old block bodies), structural validation, atomic publish, the startup decision (opt-out switch → double-execution scan → load), the legacy-block scanner behind `migrate-rc`/doctor, and the one-time hint marker. Gate cases 359.4–359.9 and the 359.12 structure ratchet are pinned in `tests/rc_readonly_loader.rs`; the retired writers stay in `assets.rs` as an unwired rollback zone (design §4.3) with the ratchet asserting they are reachable from no verb.
- Shell-face startup is a measured contract (`scripts/perf/shell-face-startup.py`):
- execution-level `-c exit` timing for every installed face — the `niu.exe`
- payload, the `winuxcmd\bin` / `winuxcmd\usr\bin` `bash.exe`/`sh.exe` links,
- and the `C:\Program Files\Git\bin\bash.exe` convention slot — with a
- verify-before-record rule (a face that cannot execute prints the failure
- note, never a fast number), structural annotations (size, hardlink count,
- same-file-as-payload), a `Measure-Command` cross-check mode, and a
- deterministic `--check-links` gate: every declared face must be the same
- file as the payload `niu.exe`. A measured-time-ratio gate was tried first
- and rejected with data — shimmed faces measured 1.04–1.46x the payload
- while healthy relinked faces measured 0.91–1.26x, overlapping intervals,
- so no ratio separates the states without false kills (niubash#377 CR).
- Measured on the 1.4.0
- install (faces still the 243,200 B rubash forwarders): payload
- `niu -c exit` 27.2 ms avg, faces 42.1–47.3 ms (+9.8 to +15.0 ms, one extra
- process), the .NET slot 80.6 ms (+48 ms, two extra processes); after
- `--relink-shells --force` on the same engine the faces measure equal to the
- payload within noise (23.8–29.7 ms, same inode) with `$0`, `BASH_VERSION`,
- POSIX mode and the `NIU_SHELL` contract verified through the links.
- The installer sweeps retired shim shapes out of the PATH entry directory
- (niubash#376 hygiene): `{app}\winuxcmd\bin` and `{app}\winuxcmd\usr\bin` are
- cleared of `*.bak-*` / `*.old-*` / `*.niu-old-*` on install and uninstall —
- the same dead-weight class as the existing `niu.exe.old-*` sweep, observed
- live as `bash.exe.bak-1.4.0` / `sh.exe.old-shposix` (4 × 226,816 B on real
- machines; `*.niu-old-*` covers a relinker crash between park and drop).
- Live entry names are never matched. The PATH entry directory contract
- (hardlinks only, `usr\bin` off `PATH`, declared entry set) is now documented
- in `docs/src/installer.md`.
- The installer's slot contract is pinned by tests, not by a comment
- (niubash#398): the ruling that the install tree never claims the
- `C:\Program Files\Git\bin\bash.exe` convention slot — the installer runs
- `PrivilegesRequired=lowest`, cannot elevate, and the slot directory is
- admin-walled — lived only in a GitHub comment until now.
- `tests/installer_surface.rs` guards the surface statically: no absolute
- foreign path or `Program Files` reference anywhere in `niubash.iss`, every
- `[Files]`/`[InstallDelete]`/`[UninstallDelete]`/`[Icons]`/`[Run]` surface
- confined to `{app}` and Inno-managed shell folders, `HKCU\Environment\Path`
- the only registry value written, and the delete sweeps matched against the
- sanctioned retired-shape globs only. `scripts/installer-slot-probe.py` is
- the runtime leg: a pure user-directory sandbox replays the executable
- installer steps (`--relink-shells`, app-tree teardown) across a full
- install/uninstall cycle and asserts the decoy foreign slot — and the real
- Git Bash slot, read-only — byte-identical at baseline, after install and
- after uninstall, with no face name left outside the removed tree. The same
- probe records the niubash#377 quantification: forwarder faces cost one
- extra process per `-c` call (chain evidence: 2 processes below the probe,
- min ~36 ms) and collapse to the payload itself as hardlinks (1 process,
- min ~19 ms, same file as `niu.exe`).
- The factory prompt's git segment runs the user's own git instead of the
- in-process libgit2 dependency: one bounded
- `git status --porcelain=v2 --branch` inside a repository, and no spawn at
- all outside one (a few `stat`s, where `__git_ps1` forks anyway). Fidelity
- is the reason to switch — sparse checkouts, linked worktrees, submodules and
- `safe.directory` refusals are answered by the git the user actually runs
- (libgit2 silently dropped the branch segment on old-owner trees once) — and
- the cost is unchanged in practice: measured p50 26 ms / max 37 ms per prompt
- inside a repository, i.e. the bare git call and nothing more, against Git
- Bash's `__git_ps1` 100-300 ms (three to five forks). Isolation rules, one
- per incident: never through a shell (a missing git is an OS error, not
- `command not found` noise), stdout captured and stderr discarded (git
- warnings never reach the screen), `--no-optional-locks` +
- `GIT_OPTIONAL_LOCKS=0` (never takes `index.lock`), `-c core.fsmonitor=false`
- (never starts an fsmonitor daemon from a prompt), a 2 s timeout with the
- child killed, and no `CREATE_NO_WINDOW` (measured +28 ms per call: it makes
- Windows allocate a hidden console for every prompt; a console child inherits
- ours and shows no window). This also drops the `git2`/libgit2 dependency:
- ~1.28 MB of the binary and 323 locked packages (libgit2-sys, url/idna/ICU,
- cc, pkg-config, vcpkg, ...). Both niubash#145 startup invariants still pass
- (`legacy_niu_plugins_rc_reaches_prompt`,
- `legacy_framework_rc_reaches_prompt`): nothing is waited on before the first
- paint, and the prompt-scoped child is reaped by the prompt itself. A
- brand-new repository (unborn HEAD) now shows its branch, where libgit2's
- `head()` errored and the segment vanished.
Changed
- The user rc is read-only to plugin management — the generated loader replaces the managed block (niubash#359 case A, 1.4.2). The control plane (`~/.niubash/plugins.toml` + `~/.niubash/sources/registry.toml`) now drives a derived activation plane: `niu plugin` verbs render and publish `~/.niubash/generated/plugins.niu` (deterministic, byte-stable when the manifest is unchanged, validate-then-atomic-publish so a failed generation never replaces the last good one and a concurrent reader never sees a missing file — the #304 invariant moves to the loader). The host sources that loader BEFORE the us
- Double-execution guard for the migration window: the loader generation records the managed-block marker pairs it supersedes; at startup the host scans the rc read-only and keeps the loader OFF while any superseded block still executes (rather than run the plugin set twice), with a one-time hint naming `niu plugin migrate-rc`. Removing the old block by hand — the only removal there has ever been — turns the loader on. `niu doctor` reports the transition state read-only.
- New verb `niu plugin migrate-rc [--diff]`: the read-only migration diagnostic. It scans the rc for legacy managed blocks (exact and hand-edited marker shapes, with line ranges and a begin-without-end malformed state reported, never fatal), publishes the candidate loader generation from the current manifest, records the superseded markers, and with `--diff` writes the suggested post-migration rc under `~/.niubash/candidates/`. It never modifies the rc — the removal is always the user's own edit.
- Rollback switch: `NIU_PLUGIN_LOADER=off` (`off`/`false`/`0`/`no`) stops the host from loading the generated loader — plugins return to manual-source mode per the documented guidance. The switch only disables loading; it never writes anything, and unsetting it rolls forward.
- Control-plane writers are atomic (design §4.4): `save_spec` (`plugins.toml`) and `write_source_registry` (`sources/registry.toml`) swap whole files through a same-dir temp + replace rename, so a concurrent reader never observes a truncate window.
- clippy debt: `needless_borrow` cleared in `niubash-runtime` — first batch of niubash#450. `history.rs` passed `&mut state` — a `&mut HistoryState` already returned by `lock_mut()` — to `refresh_if_stale`/`sync_state`/`file_signature_lossy`, so each call built a `&mut &mut HistoryState` the compiler immediately dereferenced (11 sites across `reload_from_file`/`clear_memory`/`save`/`update`/`clear`/`delete`); `plugins/assets.rs` passed `&OMB_BLOCK` — a `&str` const — to `seed_legacy_block` in three rc fixtures. With the redundant borrows gone, the six `let mut state` bindings no longer need `mut
- clippy debt: `niubash-runtime` cleared to zero — niubash#450 closes. The 105 unique warning sites left after the `needless_borrow` batch are resolved across ~40 categories: `question_mark` let-else rewrites in `shell.rs`, `is_some_and`/`is_none_or` for `map_or`, `contains`/`contains_key` for `iter().any`/`get().is_none()`, `strip_prefix` for manual prefix slicing, `clamp`/`is_multiple_of`/`is_ascii_uppercase` for hand-rolled checks, `matches!` for the keymap-target bool match, `#[derive(Default)]` for four hand-written impls, `sort_by_key`, `io::Error::other`, `c""` literals over `b"...\0"`, `
- Windows path contract: the bundled WinuxCmd coreutils own the shell tree —
- they receive the shell's POSIX spellings verbatim and resolve them in-child —
- while every other child, native or a foreign POSIX runtime such as MSYS2 or
- Cygwin, always receives native Windows paths, with no per-child probe and no
- `MSYS_NO_PATHCONV`-style switch (unixwin/niubash#346). This rides the
- WinuxCmd shell-tree layer, so the bundled WinuxCmd pin must be bumped in the
- same release.
- Windows releases bundle niu-git 2.55.0.5 (`scripts/release/preinstall.json`).
- niu-git moves to `layout: flat`: the payload lands under `opt\niugit` and
- `mingw64\bin\git.exe` is mapped straight onto `usr\bin\git.exe`, so the `git`
- on PATH *is* the git binary — no winuxcmd dispatch hop, no launcher, no MSYS
- runtime and no perl in the process tree. `git --version` drops from 72–83 ms
- to ~14 ms (Git Bash: 45–103 ms) and `git status` to ~23 ms. Needs WinuxCmd
- 1.1.9 or newer (the POSIX-unlink hardlink break, unixwin/WinuxCmd#1183);
- the release pin already carries 1.1.10.
Fixed
- A DSR-silent terminal no longer kills the shell, and TAB completion
- commits (unixwin/niubash#373, the #355 follow-up). Interactive startup
- used to anchor the prompt with a `ESC[6n` cursor-position query and ABORT
- the line editor when it timed out: on a terminal that never answers
- (dumb terminals, `script(1)` recordings, some serial/tmux links) every
- startup paid a ~2.6 s stall, printed
- "niubash: line editor error: The cursor position could not be read within
- a normal duration", and exited before painting any prompt. The editor
- (reedline 0.50 -> 0.52) now treats the query as advisory — the prompt
- paints without a reply and later cycles stay interactive; Windows/ConPTY
- is unaffected either way (conhost withholds the client at session attach
- until the terminal answers, a terminal-side contract below niu, pinned by
- the new `tests/issue373.rs` harness). File completion now commits
- like command completion: `cat uniqfi<TAB>` splices the completed word
- into the line (the unique match commits outright, the ambiguous set
- commits its common prefix and still shows the grid) instead of leaving
- the suffix as a menu preview that Enter executed unexpanded — and Enter
- submits the line as typed (GNU accept-line), dismissing the grid, where
- reedline 0.52's menu would have consumed the first Enter as an
- acceptance. The dismissal rides only the completion TAB window: a
- Ctrl+R history search's accepting Enter stays the bare event reedline's
- search handler implements (a whole `Multiple` batch is dropped there —
- unconditionally wrapping it turned the search's Enter into a dead key). Measured TTP on the ConPTY harness is unchanged (niu 3.14 s
- vs GNU bash 3.26 s median, transport-floor dominated; internal startup
- ~70-140 ms — `scripts/perf/ttp-bench.py`).
- dirname/basename: slash-shaped operands stay verbatim — `dirname /a/b`
- reports `/a` instead of a drive spelling such as `A:`; the slash-drive
- argument mask now covers the filesystem-blind string tools
- (unixwin/niubash#346).
- `(A;B);)` is a syntax error on every non-interactive route, like GNU (unixwin/niubash#431). `niu -c '(printf a; printf b);)'` ran the subshell and exited 0 — and so did script files, `--encoded-command`, and piped/redirected stdin — where GNU bash 5.3.0 rejects the shape in every mode (`syntax error near unexpected token ')'`, rc 2, subshell unexecuted; the earlier "msys GNU tolerates the tail" record came from the `winuxcmd/usr/bin/bash.exe` launcher shim echoing niu back — rubash#852). The host fast path now parses with the engine's stray-close guard armed (`ParseLoopOptions::stray_close_is_
- Bundled gawk's CRLF output gets a byte-level release gate (unixwin/niubash#467). The `opt\gawk` payload is a text-mode mingw build — its CRT rewrote every `\n` as `\r\n` on write and folded `\r\n` into `\n` on read, while every other bundled applet does binary stdio, so `awk '{print}' > f` landed CRLF files and `ORS="\r\n"` doubled the CR. The fix ships in the bundled WinuxCmd forwarder (winuxcmd.exe / winuxcmd-shim.exe splice `-v BINMODE=3` ahead of the forwarded argv for `awk`/`gawk` — the build ignores the `BINMODE` environment variable, the `-v` assignment is its only lever, and a caller's
-
rubash Unreleased 未发布 日期未知
新增
- 脚本级函数级性能剖析器(#815):`niu --profile <script> [--profile-output <base>]`——解释器确定性插桩(非采样),在函数入口/出口(`execute_cmd.c:5200 execute_function` 家族)、简单命令分发点(`execute_cmd.c:624`,试探帧由真实分发自分类:函数/外部派发取消、其余确认为内建——无第二准入表)、外部 spawn 面、`( )` 子壳与 `$()` 命令替换独立栈帧(subst.c:7143)挂 Drop 守卫;return/exit/展开失败等中止形态经栈展开正确归因。运行时为 thread_local 调用树(self=elapsed−children,唯一 collapsed 路径聚合 count/self/total),归因三分离(内建/外部 spawn/子壳,self-time 切分面总和=100%)。输出文本调用树(self/children/calls 排序)+ collapsed-stack(flamegraph/speedscope 可渲染);报告只写文件(默认进程 temp 目录,绝不 stdout/stderr)——profile 开启时 stdout/stderr/rc 与关闭逐字节一致。关闭路径开销=每缝一次 relaxed AtomicBool lo
性能
- 字面单命令跳过词展开重建(#607,PR #665):每个简单命令在 `expand_command_words` 里付一次全量 `CommandNode` 重建(~1.34µs/次),裸 `:` 的展开恒为 identity。GNU 从不重建(`execute_cmd.c:4468` 跑存储的 SIMPLE_COM;`subst.c:13219` 无触发器文本无事可做)。白名单准入(单字面词、全部结构/重定向/赋值通道为空,`[`/`[[`/分发动词除外),共享 `preserves_word_metadata` 谓词(提取复用、永不复制),契约测试逐词锁定 fast==pipeline。`:` 循环 2.03x→1.77x GNU(~0.74µs/iter),`true` 1.86x→1.67x;`((1))` 路径按设计不动。门禁与 niubash 产品 A/B 零差异。
- 大小写改写去分配+首字符短路(#608,PR #666):`apply_parameter_case_mod` 曾对每个字符跑一次 glob matcher 并 `to_string()` 堆分配;First 算子(`^ , ~`)按 GNU `subst.c case_transform` 只测首字符,空 pattern 即 `?`。First 算子现只测/改首字符后原文追加(栈缓冲零分配),空 `~~` 走直接翻转,主循环改栈缓冲。`${s^}` 500ch x20000 ~1.42s→~0.24s(~6x),`${s~~}` ~10%。33 例 GNU 差分电池 33/33;门禁与产品 A/B 零差异。
- 标量赋值快 RHS 切片一(#609,PR #672):`t=$S` 循环 2.72x。`literal`/`$name`/`"$name"` 三形走白名单快路(字面按构造输出相等直接返回,dollar 拷贝一次借读 cell);dynamics、`set -u`、非名、IFS 标记值走通用路,绑定仍经 `apply_shell_assignment`。100k `t=$S` 1096ms→927ms(1.18x)。30 例差分电池 30/30;门禁与产品 A/B 零差异。
- 标量绑定去双存储漫步切片二(#609,PR #678):`bind_marks_free_scalar`——一次 `attr_of` 探针放行无标记形,同值跳过双写,否则原地双写(镜像不变式成立);只读照常诊断(`variables.c:3258`)。net assign 2.03µs→1.33µs(−35%),GNU 1.25µs。电池 30/30;门禁与产品 A/B 零差异。
- 算术循环机器(#607 切片三,PR #679):body 借指针重走(无逐 entry `Ast` 克隆/reader 包装),DEBUG 探针门控+体后重探,空表 reap 跳过。N=200k `:` 循环 −3.4%。6 契约测试;上游脚本逐字节一致;门禁与产品 A/B 零差异。
- if 条件分发残留(#661,PR #673):空别名表时 raw 首词即展开首词,跳过逐命令 `expand_aliases`;单裸 `:`/`true` 条件跑无标记路径(影子函数/别名回退保 `set -e` 语义,附 4 回归测试)。50 命令 else 体 −26%;`ifcase` 55/55、`casearm` 29/30(仅预存 #17);门禁零差异(niu 50/49=已知负载抖动,隔离绿)。
- case 静态臂零分配(#662,PR #669):`expand_case_pattern` 逐臂全管线+~5 String 层。默认拒绝分类器(`~` 经 GNU 探针确认为展开,掉队)命中则 `Cow::Borrowed`。每臂斜率 ~2.5µs→~1µs;`casearm` 29/30(仅预存 #17)、`ifcase` 55/55;门禁与产品 A/B 零差异。
- mapfile 批量拆分+存储(#663,PR #670):100k 行 243ms vs GNU 52ms。拆分一次解码+字节域切分(`-t` 字节域剥离),无 `-O`/`-C` 时批量直写 dense 存储,跳过中间 index map。243ms→174ms(4.7x→3.3x)。26 探针矩阵 25/26(1 预存 NUL 分歧,新旧一致);门禁与产品 A/B 零差异。剩余存储侧是后片。
- 裸 `` 全局排序(#664,PR #674):globstar 漫步深度优先,GNU `sh_sortglob` 全局重排。单 `` 调用既有 `globsort_matches` 全局重排(C/POSIX 字节序 else live collation)。repro 与 GNU 逐字节一致;glob 电池 33/34→34/34(关 glob-12 观察);门禁与产品 A/B 零差异。纯正确性片。
- 数组元素 quote 单分配快路(#680 切片一,PR #686):`quote_array_value` 逐元素 2 marker 替换+解码扫描+`ansic_shouldquote`+4 链式替换。纯可打印 ASCII(`sh_double_quote` 转义集之外)直接渲染 `"value"` 单分配。seq100k mapfile ~181ms→~99ms(~1.8x)。矩阵 25/26(预存 NUL 分歧);门禁与产品 A/B 零差异。
- mapfile 跳过整输入编解码往返(#680 切片二,PR #688):ASCII 无 carrier 字节输入解码/重编码皆恒等,直接切分(每段单分配)。seq1M −6%。4 契约测试(含 carrier 回退钉死 GNU 输出);门禁与产品 A/B 零差异。
- glob 排序一次 locale 解析(#681 切片 b,PR #689):逐比较 3 env 查询提到逐排序一次;C 下字节序、非 C 走原比较器(结果恒等)。`LC_ALL=C` 300 文件循环 −22%,默认 locale 不变。契约测试;双 locale 电池 34/34;门禁与产品 A/B 零差异。
- `export`/`readonly` 补上越界负下标 abort(#635 后续,PR #677):`export -a a=( [-2]=q )` 曾打印 AFTER rc 0。`-a/-A` 委托内嵌 `declare_builtin`(`setattr.def:240-268`),`arrayfunc.c:804-809` any_failed + DISCARD(`subst.c:11169`)。复用 #658 原语:`oob_word` 上浮、操作数循环遇 OOB 即停、Executor 层裸诊断+exit 1+#637 abort。13 例新测试;18 例 GNU 探针矩阵 18/18;门禁与产品 A/B 零差异。
- 算术票据快路(#607 切片二,PR #671):identity 字母 `(( ))` 仍付全票据。双门(语法形只读+状态门:写日志空、标识符 unset/纯十进制)跳过 shopt/nounset/标记/上下文/同步。dense 探针 ~5%(~200ns/eval),headline 不变。6 契约测试;门禁与产品 A/B 零差异。
- for-list 绑定补 dequote(#675,PR #683):`for p in "f*"` 绑了 `f`+CTLESC+`*`(长 3,全局 glob 死),只有双引号 for-list 路跳过解引用。绑定点统一走存储侧 dequote(`subst.c:4807`)。`casearm` 29/30→30/30,`ifcase` 55/55;门禁与产品 A/B 零差异。根因在 case 上游。
- quoted 复合元素保留引用态(#377,PR #730):`declare -a d3=("$x")` 存了 glob 结果。NoMatch 臂推解引用重包形式,消费端 glob 解引用文本丢引用态。现引号内序列化 CTLESC 保护;`=` 臂解保护判下标。文件 10/12→11/12(剩预存);glob 34/34;门禁与产品 A/B 零差异。
- fd_redirects 并行竞态(#667,PR #676):6 测试并行红、串行绿。`external_fd_copy_bin_dir` 返回固定路径,多测试互删助手目录。现 pid+ThreadId+计数器唯一目录。并行 10/19→19/19(3x),串行 19/19。纯测试改动。
- 平台专属测试分流(#144/#363/#555,PR #692/#693/#694):`cmd /c` 流合并 10 测、`#!/usr/bin/env` 9 测、反斜杠字面量 11 行——在非对应平台上注定红。整文件/逐行 `cfg(windows)` + `$I363_PROBE`/PATH 解析,可移植行全留。纯测试改动。
- 测试进程 CWD 隔离(#690,PR #691):`cd` 内建 chdir 测试进程(语义正确),但 1586 测共用单进程 CWD,一个 `cd` 毒死后面全部相对 `target/` 用例(~990 失败摊满所有 part)。逐测 RAII `CwdGuard`(panic 安全),1589 处机械插入,零语义改动。`executor_tests` 串行 321/1229→1540/10(剩 10 全是预存环境/抖动);门禁与产品 A/B 零差异。串行为限(CWD 进程全局)。
- respawn 传输可写探针回退(PR #752):无 TMPDIR 且 `/tmp` 不可写(Android 有不可写的 `/tmp`)时临时文件建不出,回退内联 300KB `-c` 死于 E2BIG。存在性改可写性探针(pid+计数器),一次退到进程 temp dir、二次退 CWD。6000 函数后台任务恢复;无残留文件。
- Windows-only tmpdir 测试分流(#328/#331,PR #750):`/var/tmp` 映射与 TMPDIR 自注入引擎臂都是 `cfg(windows)`,测试在外失败。`#[cfg(windows)]` 门。纯测试改动。
- 真 Unix stty(#282,PR #732):非 Windows 是空桩(恒 rc 0)。现 `isatty(0)` 探针(非终端全形态诊断+rc 1),终端下左到右执行(`size` 经 TIOCGWINSZ,raw 系经 termios),Windows 路不动。文件 2/4→4/4;门禁与产品 A/B 零差异。
- 交互 rcfile 只 source 一次(#312,PR #712):`--rcfile gate.sh -i` 跑两遍 gate.sh。`run_startup_files` 已 source 后各驱动又直调一次。用 `__RUBASH_SOURCED_RC` 去重(空 HOME/非交互扩展/login 原样保留),`#297` exit 语义不变。append 探针 2→1;文件 6/9→9/9;门禁与产品 A/B 零差异。
- 真 uid/euid 身份,去掉 root 模拟(PR #698):非 root 主机报 `UID`/`EUID` 0、`\$` 渲染 `#`。`uid_value` 回退硬编码 `"0"`(开发基线跑在 root 下的模拟)。现取真 `getuid()`/`geteuid()`(GNU `variables.c:984`,继承值保留,Windows 保持 0)。4 个 prompt 测试曾钉作者账号、`reader` 默认 prompt 钉 `#`,现按宿主校准。三方探针与 GNU 一致;门禁与产品 A/B 零差异。
- 关联数组元素写不再全量重渲染(#618,PR #650):`m[k]=v` 每次写曾做三次 O(keys) 操作(镜像字符串克隆、解析 memo 的 Vec 深拷贝、全量重渲染),填充数组是 O(n²),K=5000 时单次写比 GNU 慢 433 倍。现改为 O(1) 追加一段 `[k]=v`,预算触发规范渲染压缩重复段(均摊 O(1),串长不超 2 倍规范长度);读路径零改动(追加永不过期)。K=5000 单次写快 45 倍,与 GNU 差距 445x→9.9x,key 数涨 5000 倍单次写仅漂 1.14x。附带修好一个 O(n²) tokenizer(每段 collect 整个剩余输入,10001 段真解析 563ms→17.8ms 线性)。50 例 GNU 差分电池 50/50;全量 failure-set 门禁与 niubash 产品 A/B 零差异。
- 索引数组覆盖写不再全量重渲染(#616,PR #652):`a[i]=v` 同理 O(n²),K=5000 时 674x。覆盖/稀疏跳跃同样 O(1) 追加(indexed 读路径天然折叠重复,BTreeMap last-wins,无需改解析/渲染;每次追加同步 typed cell;`declare -p` 等原文打印在有重复时才折叠重渲染)。K=5000 快 177 倍,与 GNU 差距 674x→3.8x。附带修好 `declare -a a=([5]=x [2]=y)` 种下 dense cell 导致后续元素写复刻的正确性 bug(另见 #651)。50 例差分电池 49/50(唯一分歧即 #651);门禁与产品 A/B 零差异。
修复
- `mark_quoted_ifs_from_raw` 模拟器补上 carrier 字节识别,per-char IFS 标记不再被 carrier 词静默跳过(#868):`mark_quoted_ifs_from_raw`(`src/executor/command_prepare.rs`,#743 的实现核心)用平行状态机重读 raw 词、模拟 quote removal,仅在模拟结果与 cooked 词逐字符相等时才落 `\x1c` per-char 标记,失配即静默 bail 退回粗粒度整拆/整不拆——而模拟器把 cook 产出的每个 markers.rs carrier(`$'...'` 解码输出、`$"..."` 体、逃逸引号、逃逸 IFS 空白/模式字符)都当普通数据字节,carrier 词逐字符必失配,最需要 per-char 规则的词恰恰被静默降级。现按 lexer 权威 walk(`lexer/quotes.rs` cursor 系列)逐臂复刻 carrier 变换:逃逸符在引号内外分别 cook 成 DATA_DOLLAR/DATA_BACKTICK/DATA_SQUOTE/DATA_BACKSLASH/DATA_DQUOTE、逃逸 IFS 空白 cook 成 ANSI_C_IFS_GUARD 对(自护形,不另记位)、逃逸模式字符 cook 成 CTLESC 对
- `${var@P}` prompt 变换通道不再泄漏命令替换状态(#813):`PS1='x$(false)y'; p=${PS1@P}; echo $?` 曾打印 1——`@P` 算子通道(`apply_parameter_transform_value` 的 Prompt 臂)内嵌 `$( )` 的收割状态经替换状态 cell 被赋值边界当作替换退出规则发布。GNU 律(parse.y:6684-6697):`decode_prompt_string` 在 promptvars 遍历(`expand_prompt_string`,parse.y:6689)前后快照/恢复 `last_command_exit_value` 与 `last_command_subst_status`(subst.c string_transform 'P' 走同一 `decode_prompt_string`,`${var@P}` 同受覆盖);#604 修的是交互渲染通道(`expand_prompt_string_mut` 快照恢复 rc/PIPESTATUS),本修复把同一律落到共享 prompt 遍历引擎(`expand_embedded_parameters_double_quoted` 快照/恢复替换状态 cell,含管道 `-i` 渲染通道)与 mut 渲染通道的 cell 残留。恢复
- `set -T`(functrace)下 DEBUG trap 透传进 `$()` 命令替换(#825):`set -T; trap 'echo D:$BASH_COMMAND' DEBUG; x=$(true)` 曾打印 `got=`——`$(true)`/`$(false)`/`$( :)` 的 trivial 体状态快捷路(`expand_command_substitution_inner`,command_substitution.rs)排在 functrace+DEBUG 取消资格门之上,硬编码空输出短路从未进入替换子壳,GNU trap.c:1466-1474(`reset_or_restore_signal_handlers` 仅在 `function_trace_mode == 0` 时清除 DEBUG/RETURN)要求的继承触发完全丢失。GNU subst.c:7143 `command_substitute` 把每个体交给 `parse_and_execute`,execute_cmd.c:4648-4649 对 `true`/`false`/`:` 照发头行与 DEBUG。修复:两快捷路移到门之下(与 xtrace 取消资格同门同型),门命中即走真实 parser/executor。WSL GNU 5.3.0(1) stdin oracle 8 形态逐字节
- 复合命令自身重定向失败诊断走 fd 表、单次发送(#826):`{ :; } 2>err2.txt > /nonexistent_dir/f` 曾把诊断打印两次到真实 stderr 且 err2.txt 根本没建——brace 包装层按字段序(`redirect_out` 先于 `redirect_err`)应用重定向,fd-1 open 在 fd-2 绑定前失败;错误被 ast_exec 分发的 IoError 臂吞掉后不 `continue`,同一命令经通用路再分发、再失败、再打印(面 1/2 双打印 + 捕获丢失)。`( { :; } > /nonexistent_dir/f ) 2>err.txt` 则"捕获 + 泄漏"并存:包装层 raw `eprintln!` 绕过子壳已建立的 fd 2 绑定(面 3)。GNU(redir.c:767-955 `do_redirection_internal` 按解析序左到右;redir.c:135 `redirection_error` 经当时生效的 fd 2 报告):诊断一次、落在重定向目标里。修复:brace 四个 stdio 臂改解析序处理(位置取自 `command.redirects`),`2>`/`2>>` 文件目标记为失败报告目的地(不做常驻 fd_table[2] 绑定——常驻绑定会被 walker 下游的 `1>&2
- alias 同读守卫改单调读取生成身份,跨 read 单元不再误抑制(#817):执行级 alias 同行可见性守卫拿定义时的物理行号当身份(`__RUBASH_ALIAS_LINE_<name>` vs `__RUBASH_CURRENT_LINE`,`arithmetic_aliases.rs:867-882`),而嵌套读单元会重排行号——source 的文件从 1 重新计数、eval 串按 `caller_line+i-1` 续号(`trap_exec.rs` execute_eval_source)——定义行与外层后续物理行相撞时合法使用被误报 `command not found` rc=127,GNU 展开(规则是同读不是同行,parse.y 读时展开)。修复:定义改记 `(读取代, 行)` 二元组(`__RUBASH_ALIAS_GEN_<name>`,`ShellState::read_generation` + 单调高水位 `read_generation_ceiling`,兄弟单元永不共享身份),生成边界正是现有 `CURRENT_LINE` 保存/恢复接合点——source 在 `builtins/source/execution.rs` 的 evalfile.c:253-257 push_stream 处 push、run_unwind_frame(eval
- `set -E` ERR-trap 嵌套上下文语义:折叠压制、扁平区收口 fire、return 归因(#720,PR #831):三面均以 WSL GNU 5.3.0(1)(stdin oracle)逐字节钉死。① 嵌套子壳过火:`( (false) )` 曾 fire 3 次,GNU 2 次——execute_cmd.c:1799-1806 把整体为单个子壳命令的子壳体标 `CMD_NO_FORK`,execute_cmd.c:662-663 原位执行(无 make_child/wait_for),中间帧从不观察失败、其 :769 `run_error_trap` 永不运行;现以 `subshell_fork_collapse` 旗标圈住该形体(node 形 + `f() ( )` 扁平形两路),聚合点与 `set -e` 早转臂在其下跳过,非折叠中间层(`( (false); false )`、`( { (false); } )`)照常 fire。② 扁平函数体欠火:`f() ( false ); f` 曾 fire 2 次,GNU 3 次——子壳完成态的 ERR 裁定属于 wait 帧(execute_cmd.c:756-778,无 `&&/||` 豁免;`$LINENO`=子壳 `)` 行 execute_cmd.c:696;`BASH_COMMAND`=区文本,:703
- 活动控制台 `read` 三面:`-t` 门饿死、deadline 行读被 conhost 预煮劈半、onechar 缺 ICRNL(#806,PR 于 `fix/806-read-conpty`):① `wait_readable`(`src/fd/windows_impl.rs`)只 peek 队头 1 条记录并要求 key-down——行编辑提交 Enter 的 key-RELEASE 卡在队头时,其后可消费的 key-down 永远不被看见,`read -t 2 -n1` 烧满 deadline 返 142 变量空(GNU select 同形 0.2s 取到键,WSL 5.3.0 oracle)。现 peek 16 条一批,批内任一"带字符的 key-down"(ReadFile 能吐出的)即 Ready,peek 不消费次序不动(niubash#280 的"空队列永不 Ready"保护原样保留)。② console 上的 deadline 读改走 one-char 路(`read.def:570-590` ttfd_onechar;read.def:659 每字节 check_read_timeout;GNU 的 zread 从不预煮 tty 行,read.def:736):cooked 行纪律与 raw 队列是两个缓冲,首次 cooked `ReadFile(1)` 把
- tilde 与 cd/pushd/popd 共用同一 $HOME 基准:GNU 缺失/空值之分(#754):path model 家族——`HOME=` 置空后 `cd ~` 曾静默落进 USERPROFILE(rc 0),同脚本裸 `cd` 却报 "HOME not set";`$(cd && pwd)` 快路对置空 HOME 走 tilde 回退输出 USERPROFILE;`pushd ~` 空展开是无声成功、`pushd -n ~` 把 cwd 规范化后入栈(GNU 推原词,栈为 `<cwd> ""`)、`popd ~` 空词误报 invalid argument。GNU 规范(`lib/tilde/tilde.c:344-356` tilde_expand_word):`~`/`~/` 恒按展开时点的活 cell `sh_get_env_value("HOME")` 粘接(`glue_prefix_and_suffix` tilde.c:304-317,空值是真值——`HOME=; cd ~` 即 `cd ""` 报 "null directory",cd.def:331-336),仅变量缺失才落到回退链(_WIN32 APPDATA tilde.c:349-350;passwd `sh_get_home_dir` variables.c:736,getpwuid 固定于
- PROMPT_COMMAND 执行后 `$_` 按 GNU 括回(#797):bash-preexec 类资产 source 后,DEBUG trap 里的 `preexec "$_"` 拿到的是 PC 内部最后的命令词(`bp_install`/`bp_precmd_invoke`)而非用户上一条命令的尾词。GNU `execute_variable_command` 在 PC 的 parse_and_execute 外括 `save_lastarg`/`bind_lastarg`(`parse.y:3017`、`3022-3023`;`save_parser_state` 不携带 lastarg,`7248/:7250` 只管 `$?`/PIPESTATUS);exit 跳转路径跳过恢复,与 `$?` 恢复同一规则。`bind_lastarg(NULL)` 绑空串(`execute_cmd.c:4192-4193`)。piped `-i` 最小 bash-preexec 形与 wezterm 全资产 transcript 与 WSL GNU 5.3.0 逐字节一致;回归 `tests/issue797_bash_preexec_debug_trap_regressions.rs`。
- source 的 DEBUG trap 作用域改表驱动(#797):source 内安装的 DEBUG trap 曾在本次 source 余下命令上全部哑火(`-c`/脚本/交互三路同病)。GNU `source_builtin` 只快照+摘除入口 trap(`source.def:209-217` `restore_default_signal`),文件内新装的是活表状态照常触发(`trap.c:750-756`),unwind-protect `trap_if_untrapped`(`trap.c:737-743`)仅在退出时 DEBUG 无 trap 才归还入口 trap,且排在 RETURN trap(`evalfile.c:395`)之后。旧的 duration flag 对"入口本无 trap + 文件内新装"过宽。ignore 边界(CR R1/R2/R3):`trap '' DEBUG`(IGNORE,trap.c:900 同置 SIG_TRAPPED)与 `trap - DEBUG` 按 trap 表成员资格(`__RUBASH_TRAPS`)区分而非 action 串内容——入口 ignore 不快照不摘除(`TRAP_STRING` 对 ignored 返回 NULL,trap.h:57-59),文件内 ignore 令 `trap_if_untrapped`
- #798 判定面钉死:source 中 `exit 1` 语义与 spawn 失败 `$?` 与 GNU 等价:LibreSSL testenc.sh 沙箱形态下 `cat openssl.cnf >p` 先截断后失败 → `cmp` 比的是两个空文件 → `exit 1` 本就不可达,47-cipher 循环跑穿是正确行为(WSL GNU 5.3.0 同样整循环,stdout 100 行/stderr 201 行 rc 0 逐字节一致;同机 rubash 4.5s < msys bash 5.2 5.3s,CI HANG 是 2 核 runner 8 并发下 ~400 次 spawn 的体量问题)。真实失败形态(cmp 差)首个失败即整体 rc 1 退出、交互会话同终止。回归 `tests/issue798_testenc_exit_in_source_regressions.rs`(hermetic:cmp/rm 用函数替身,钉 spawn-fail 127、空重定向目标、循环+if 形的 exit 终止)。
- 算术下标补上 `set -u` 检查(写路径)(#639,PR #654):`set -u; a[k]=v` 曾静默存储 rc 0。GNU 所有下标经 `evalexp → expr_streval` 统一设卡(`expr.c:1190-1216`);写路径求值器补上与兄弟函数相同的 prologue,五处下游诊断在 flag 下静默, abort 走 FORCE_EOF 通道(`-c` 下 127、脚本 1),另用独立 cell 区分 DISCARD 以免状态错位。25 例差分电池 10/25→25/25;回归文件 `tests/issue639_nounset_subscript.rs`。
- 复合赋值 `[@]`/`[*]` 报整词拒绝而非算术错(#636,PR #656):`a+=( [@]=q )` 曾报 `@: arithmetic syntax error`,GNU 在算术求值前整词拒绝(`arrayfunc.c:786-792`,`cannot assign to non-numeric index`)。两处 compound 臂加精确匹配 guard(不 abort,走 GNU break 语义:保留部分存储、eval 可包含)。15 例差分电池 4/15→14/15(剩 declare-abort 例归 #635);回归文件 `tests/issue636_atstar_compound.rs`。
- `eval` 包含赋值 DISCARD 跳转(#637,PR #657):`eval 'a[-2]=q'` 曾结束整个脚本。GNU 在 eval 边界捕获 DISCARD(`evalstring.c:365,422-442`),eval 返回失败、调用者继续。新增 bad-subscript class marker,eval 边界精确吸收(算术/nounset 照常逃逸);eval 内按行继续,返回末行状态。20 例差分电池 9/20→20/20;回归文件 `tests/issue637_eval_discard.rs`。
- declare/local 越界负下标 abort 而非静默丢弃(#635,PR #658):`declare -a a=( [-2]=q )` 曾打印 AFTER rc 0。builtin 解析器区分无头/越界,扫描中断并保留部分存储,报错词经 out-param 上浮到 Executor 层做 fatal 诊断+abort(复用 #637 原语实现 eval 包含)。20 例差分电池 6/20→20/20;回归文件 `tests/issue635_builtin_oob.rs`。`export`/`readonly` 的同类 abort threading 记作后续。
- 函数定义 stdin 优先于调用点 stdin(#655,PR #684):#653 把优先级翻转了(`f() { cat; } < deffile; f < callfile` 打印 call)。GNU 调用点在外、定义在内(`execute_cmd.c:5205`),定义装了活 fd 0 就赢。`part_054` 14/14;门禁与产品 A/B 零差异。
- 复合赋值字段分裂传输保留空字段(#298,PR #762):`IFS=.; A=($1)`(`a.b..b`)存了 3 个元素(`b" "`),GNU 存 4 个。`compound_field_split_transport` 对空字段发合成 tag+空格,重分只在空白 IFS 下认;现空体等下个定位 gap,用真实分隔符按序分发,空白 run 仍坍缩一边。`a.b..b`/`a::b`/`:a:` 全对 GNU;298 文件 12/12;GitHub CI 全绿(本地 master 腿 diff 因设备击杀循环未竟,见 PR 备注)。
- issue358 探针目录沙盒化(#358,PR #766):`mkdir -p /tmp` 在 Android 失败→`cd`失败→`touch zzq` 落 repo 根(测试靠自身污染通过)。temp_dir pid 唯一目录,用后删除。14/14,无残留。纯测试改动(#763/#765 同类)。
- fd 偏移测试经 PATH 解析外部 true(#655 后续,PR #687):硬编码 `/usr/bin/true` 在 Android(无 /usr/bin)上 127。测试时经 PATH 解析(绝对路径绕过 builtin 仍是外部 spawn),缺席则跳过。双测 Android 绿。纯测试改动。
- prompt 渲染不再破坏读者的 `$?`/PIPESTATUS(#604):交互驱动(niu 原生控制台通道,`expand_prompt_string_mut`)渲染 `PS1`/`PS2`/`PS0` 时,内嵌 `$(false)` 等命令替换的退出状态此前经赋值展开引擎的替换边界写入 `exit_code`,下一个命令的 `$?` 读到 1;GNU 的 prompt 渲染是读侧操作,从不动读者的状态(WSL GNU Bash 5.3.0 piped-`-i` 探针:`PS1='$(false)'` 渲染后 `echo $?` 仍打印渲染前的值)。修复:渲染通道快照并在收尾恢复 `$?` 与 PIPESTATUS——赋值 RHS 通道(`v=$(false)` → rc=1 的 POSIX 赋值退出规则)不受影响。附带 #604 的通道 parity 回归与相对性能上限测试(PS1 渲染路径 vs PROMPT_COMMAND 路径的中位耗时比值上限),并按 profile 数据澄清该 issue 的主刀:四主题 rr 成本为外部 spawn 数 × Windows spawn 地板(每次渲染 4-6 次 git spawn;裸 `std::process::Command` 对同一 git.exe 需 208.9ms,rubash 会话内 217.6ms),两条引擎通道执行
变更
- wrapper 重spawn 的 argv 握手旗标按消费者在位门控(#873 后续):`--internal-bg-wrapper=<nonce>` 是 niu CLI 专属旗标,此前 wrapper 重spawn 无条件附加——嵌入式宿主(niubash 的 niu 启动器)与任何不经 `consume_background_wrapper_handshake` 的 CLI 会把它当未知选项拒收(`bash: --internal-bg-wrapper=<nonce>: invalid option` + usage),后台命令 spawn 即死(niubash master CI background_spawn_args 两红)。现旗标仅当本进程 CLI 在启动时调用过 consume(`background_wrapper_flag_consumer_present()`,rubash main.rs 每次启动必调)才附加;非消费者宿主得到 env-nonce-only 重spawn——env 键对非消费者惰性(不被读、消费者启动即清洗),单通道永不 arm(#866 契约不变)。rubash 本体行为逐 spawn 等价(消费者恒在位);宿主采纳 consume 即恢复完整双通道握手与 #737 cascade。
- Windows 无宿主 sudo 回落 PATH 执行(#810 行为变化披露,#813 补记):`sudo cmd` 在 Windows 且嵌入宿主未注册 elevation handler 时,曾一律由 host builtin 应答 "elevation handler is not configured"(rc 1),PATH 从不被咨询;自 #810 起该名字按外部命令走 PATH 解析——有 sudo.exe 的机器(Win11 System32)真实提升执行,没有的报 command-not-found rc 127,`builtin sudo` 无 handler 时报 `builtin: sudo: not a shell builtin`(rc 1)。introspection 契约不变:`command -v`/`type -a`/`enable -a`/`compgen -b` 依旧把 sudo 报告为外部命令(docs/builtins.md §3 隐藏 builtin 条款)。方向与 GNU 更近(GNU 无 sudo builtin)。#813 以两条 windows-only 测试钉死两臂执行路由(假 sudo PATH 实跑、`builtin sudo` rc 1)。
构建
- 发行来源戳记补 dirty 工作树摘要(#827 建议 3):`--version` 的 `build:` 指纹此前只铸 `git rev-parse --short HEAD`——哈希诚实但不担保内容,v1.4.0 事故正是"banner 报某 commit、二进制实际编译了该 commit 加未提交改动"(#488 家族盲区:哈希可见、工作树内容不可验证)。现携带未提交改动的树铸 `<rev>-dirty-<8 hex>`,digest 为 `git diff HEAD --binary` 的 SHA-256 前 4 字节(暂存/未暂存改动皆入;未跟踪文件不置 dirty,同 `git describe --dirty`/`diff-index --quiet HEAD` 语义):同内容同 digest,任何改动必改 banner,两个不同脏树不再可能印出同一戳。`RUBASH_BUILD_HASH` 显式钉值不能洗白脏树——钉值由 crate 侧 `option_env!` 直读(rustc dep-info 自带失效跟踪,改钉即重编),build.rs 把 git 派生戳发在独立变量 `RUBASH_GIT_STAMP`、脏半段单发 `RUBASH_GIT_DIRTY_SUFFIX`;钉值只赢修订半段,仓库在场时 `-dirty[-<digest>]` 仍追加。build.
- 命令替换 pipeline-filter 死链删除(#710):`command_substitution_pipeline_filter` 的 8 条 filter 臂(sort/sed/tr/head/grep/wc/tail/uniq)连同其唯一调用链——`command_substitution_pipeline_output`、`timed_command_substitution_inner` 的 `|` 臂、`command_substitution_pipeline_first_stage`、孤儿助手 `split_pipeline_words`/`echo_raw_output`——自 rubash#117 平凡性 gate 之后不可达:gate 字节白名单拒绝 `|`,能抵达 filter 的替换体不可能含管线;且该链还只住在 `time` 前缀快捷方式内,进一步收窄。运行时复核:入口探针在 producer×filter 电池(含反引号、嵌套 comsub、alias 注入 `|`、`cat`/generic 首段、`time` 前缀形)上零命中;阳性对照(gate 临时放行 `|`)全部探针命中并复现休眠特征(`$(…|wc)` 默认单列 `1`,而活跃路径是三列)。`$( … | … )` 的可观测行为本就由真执行器管线(pipeline_exec)与外部
-
oh-my-niu Unreleased 未发布 日期未知
更新
- Release artifact: `oh-my-niu-2.0.0.zip` (`api = "niubash:[email protected]"`,
- `min_niubash = "1.0.0"`).
- Host compatibility: the `git` prompt segment (`[segments.git] id = "vcs"`
- in `prompts/segments.toml`) now resolves to niubash's native
- `SegmentId::Vcs` as of niubash 1.4.1 — branch / `branch*` / short SHA,
- themed `[git_clean]` cyan / `[git_dirty]` yellow, rendered in-process with
- zero child git processes. No bundle format change: every shipped preset
- already carries the segment (the `lean` preset is the documented showcase).
- Renamed the bundle from `oh-my-winuxsh` to `oh-my-niu` to match the
- niubash 1.0.0 brand. Installed `~/.oh-my-winuxsh` directories keep
- working: niubash discovers the pre-rename layout as a fallback.
- Bundle/plugin API identifiers move from the `winuxsh:` prefix to
- `niubash:` (`niubash:[email protected]`, `niubash:[email protected]`,
- `niubash:[email protected]`). Manifests may declare either
- `min_niubash` or the legacy `min_winuxsh` field.
- Entry point renamed to `oh-my-niu.winux`; scripts now use the `NIU_*`
- environment prefix (`NIU_PLUGINS`, `NIU_THEME`, `NIU_THEME_PLUGIN`)
- and the `NIUBASH` bundle-root variable.
- New first-party plugins: `command-timer` (per-command duration and exit
- code), `auto-env` (automatic `.venv` activation per directory),
- `dirmarks` (directory bookmarks), `git-fetch-reminder` (upstream
- divergence notices), and `env-sync` (push/pull shell config and the WPM
- package manifest through any rclone remote).
- All first-party pack manifests move to version 2.0.0 to match the
- bundle release.