dsh-bash-native
Rust-based POSIX bash executor for the Windows platform — gives DSH a native bash with no WSL or MSYS compatibility layer, honors DSH's three-tier permission policy, and provides persistent-terminal tools.
Install
dsh plugin --profile web add dsh-bash-native
Sandbox test results
| Stage | Result |
|---|---|
| 1 install | ✓ |
| 2 list | ✓ |
| 3 mount | ✓ |
| 4 boot | — |
Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).
README
<!-- ═══════════════════════════════════════════════════════════════════════ -->
<div align="center">
🐚 dsh-bash-native
Rust 实现的 Windows 原生 POSIX bash 执行器
给 DSH 的 shell 提供一个真正原生的 bash —— rubash 引擎,不依赖 WSL,也不需要 MSYS 兼容层
<br>
适配三档权限策略 · 后台作业 · 支持持久化终端
<br>





中文 · [English](README.en.md)
</div>
🐟 为什么需要它
~~避免肥鱼被 cmd 咬~~,也规避了子系统和 MSYS 兼容层的性能开销
0.3.1:引擎由brush更换为rubash,修复多项已知缺陷,提供更好的原生 POSIX 兼容
0.3.3:引擎升级到 rubash 1.5.0——三笔补丁 150-001–150-003,旧补丁随上游合入退役
<p align="center">
<img src="img/example0.jpg" width="240">
</p>
<table align="center">
<thead>
<tr>
<th align="left">方案</th>
<th align="left">命令在哪里执行</th>
<th align="left">每条命令的代价</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Git for Windows</b></td>
<td>MSYS2 兼容层上的移植 bash,POSIX 语义由运行时模拟</td>
<td>兼容层每次起子 shell 或外部命令都要完整 fork 一个 MSYS2 进程(挂起线程、另起进程、经管道搬运内存),开销随子 shell 启动频率线性放大</td>
</tr>
<tr>
<td><b>WSL</b></td>
<td>命令在 Linux VM 的发行版里执行</td>
<td>每条命令都要经 Windows/Linux 边界往返一次,延迟对高频短命令尤为不利</td>
</tr>
<tr>
<td><b>本插件</b><br><sub>rubash 引擎 + 工具链</sub></td>
<td>纯 Rust 实现的原生二进制,无模拟层、无 VM</td>
<td><code>grep</code>、<code>sed</code>、<code>awk</code> 等来自随包工具链,其余由引擎在进程内处理,没有兼容层或 VM 边界</td>
</tr>
</tbody>
</table>
⚡ 实测:启动与子 shell 的代价
同一台机器(Windows 11 / x64)上的同一组脚本,每项取 5 次最小值,负载只用 shell 内建与一次外部进程,排除工具链差异。
复现:仓库里的 node scripts/bench-engines.mjs。
工作负载 · rubash(本插件) · Git Bash / MSYS2
bash -c :(启动到退出) · 7 ms · 25 ms
echo hi · 6 ms · 26 ms
算术循环 10k 次 · 65 ms · 75 ms
[ ] 判定 20k 次 · 167 ms · 181 ms
命令替换 300 次 · 11 ms · 3052 ms
子 shell 300 次 · 25 ms · 2497 ms
外部进程 20 次(cmd /c exit) · 192 ms · 560 ms
差距来自 fork:MSYS2 每次命令替换与子 shell 都要 fork 一个兼容层进程(约 8–10 ms),rubash 两样都做在进程内(每次约 0.04 ms),300 次就相差 2–3 秒。
🧭 路径不会被“自动替换”
Git Bash / MSYS2 把参数交给原生程序时会猜哪些像路径并改写(/flag → C:/Program Files/Git/flag,于是脚本和 agent 被迫记着 MSYS_NO_PATHCONV=1);连数据位也一起改:用 MSYS 调原生 awk.exe 跑 awk '/x/{print}',程序位到手是 X:/{print},直接语法错误、进错路径。
rubash 不猜:按子进程方言,整条 argv 要么原样交给带 POSIX 层的程序,要么统一翻成 Win32 拼写;随包工具的脚本、模式、程序位永不改写,极大减少了找不到路径的情况。
📦 安装
CLI 安装
以 web profile 为例
dsh plugin --profile web add dsh-bash-native
dsh 网页版 / 桌面版安装
dsh 插件页面 → 右上角 添加插件 → 填入插件名 dsh-bash-native 或仓库地址:
https://github.com/SpookyWaste/dsh-bash-native
重启后,在 Settings → General 里把 Native Bash (Windows) 设为默认 preset(或在会话里切过去)。
持久化终端组件默认关闭,在插件管理中可以打开。见[持久终端组件](#持久终端组件)。
<table>
<tbody>
<tr>
<td><b>平台</b></td>
<td>Windows x64</td>
</tr>
<tr>
<td><b>DSH</b></td>
<td><code>>=0.2.0-rc.2 <0.3.0-0</code>(0.2.0-rc.2 - 0.2.1-rc.5实测)</td>
</tr>
<tr>
<td><b>持久终端组件</b></td>
<td>需要 DSH <code>>=0.2.1-alpha.1</code>才能开启</td>
</tr>
</tbody>
</table>
⚙️ 引擎
引擎是 rubash(unixwin/rubash,Rust 写的 bash 5.3 兼容 shell,MIT),本仓库从源码构建它、按 engine.lock.json 里的 commit 应用三笔补丁,产物 engine/win32-x64/rubash.exe(约 14 MB)随包提交。
兼容 MSYS 的uname -s身份(MSYS_NT-…),所以脚本里按 MSYS 分支的逻辑也能成立;并发、取消与作业语义由引擎自己实现,跟 MSYS 运行时无关。
<details>
<summary><b>🧩 补丁清单(点击展开)</b></summary>
<br>
<table>
<thead>
<tr>
<th align="left">补丁</th>
<th align="left">做了什么</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>150-001-cd-into-virtual-roots-with-a-real-directory</code></td>
<td><code>cd</code> 进 <code>/tmp</code> 这类<b>背后有真实目录</b>的虚拟根时,把<b>会话 cwd</b> 也一起移动(1.5.0 里 cwd 是会话状态,不再是进程 cwd):原来只改 <code>PWD</code> 就返回,于是 <code>pwd</code> 报新目录、相对操作数与子进程却还在旧目录。<code>PWD</code>、<code>pwd -P</code>、<code>DIRSTACK</code> 仍然显示 <code>/tmp</code>;<code>/usr</code>、<code>/etc</code>、<code>/bin</code> 没有目录可去,<code>/</code> 没有唯一目标,这四个名字保持只改逻辑状态。</td>
</tr>
<tr>
<td><code>150-002-default-a-unset-locale-family-to-utf-8-on-windows</code></td>
<td>没设 <code>LC_ALL</code>/<code>LC_CTYPE</code>/<code>LANG</code> 时,Windows 上默认按 UTF-8 字符语义:<code>${#中文}</code> 是 2、<code>[[:alpha:]]</code> 认 CJK、<code>${v:0:2}</code> 切两个字符。1.5.0 把空的 locale 族解释成 C locale(按字节),这条把它改回 1.4.3 的默认;显式写 <code>LC_ALL=C</code> 仍然按字节。</td>
</tr>
<tr>
<td><code>150-003-identity-tools-answer-without-a-path-program</code></td>
<td><code>PATH</code> 上没有 <code>uname</code>/<code>arch</code> 程序时仍由引擎作答(MSYS 人设):1.5.0 的进程内仿真要先解析到程序,否则让位给一次必然失败的 spawn。可解析的一侧不变——<code>PATH</code> 上的同名脚本仍然会运行。</td>
</tr>
</tbody>
</table>
</details>
💡 每次解析都按 lock 里的 sha256 校验产物,并按盖章跳过重复校验:产物的 size 与 mtime 记进%LOCALAPPDATA%\dsh-bash-native\verified\,没变就不再读那 14 MB(verifyArtifacts: 'always'可恢复每次都哈希)。
🔧 实现
- 注册
ctx.shell:ShellExecutor的 Service Provider,构建在@deepseek-ai/dsh-bash-local之上,每条命令交给解析到的 rubash 引擎;引擎与工具链都随包分发,并在解析时按 lock 与 manifest 校验。 - 服从 DSH 的三档文件策略(
read-only/workspace-write/danger-full-access):受限档位把引擎 argv 交给ctx.sandbox,不限档位直接 spawn 并如实报告档位。 - 后台作业经
ctx.jobs汇报,超出内存上限的输出落到 spill 文件、路径随结果返回;echo、cat、sort等空PATH也能跑,因为随包工具链先于宿主PATH。 - Web 面用自带的 agent preset 挂载,agent 面用 overlay 显式启用;它不替换宿主层的
ctx.shell,因此其他 preset 的pwsh工具不受影响。
🎛 两个预设
[cordis.patch.yml](cordis.patch.yml) 只有一行注册器(dsh-bash-native/presets),
…(truncated)
Rendered from the README published on npm. Formatted only, not edited.
Versions
Published on npm::10,最后更新 2026-11-04