Paths & MSYS: the biggest behavioral difference
For every predecessor of "bash on Windows", paths are the compulsory question: either rewrite
arguments heuristically before spawn like Git Bash, or force a new /mnt/c dialect
like WSL. niubash's answer is a third one: all four ecosystem path dialects are accepted
and genuinely folded, while argv itself arrives byte-for-byte verbatim — because path
translation is a table-driven engine responsibility, not a compatibility layer hiding inside
child processes.
Four dialects, one engine accepts them all
| Dialect | Example | Folding behavior |
|---|---|---|
| Windows native | C:\repo, C:/repo | First-class citizen — the native form is the shell's internal representation; there is nothing to "convert to". |
| MSYS / Git Bash form | /d/repo, /tmp, /usr/bin | Native children receive D:\repo; /usr/bin, /etc, /tmp map to real Windows directories under the install root. Exception: cmd.exe receives /d/repo verbatim (/d is indistinguishable from its own switches). |
| WSL form | /mnt/d/repo | Genuinely folded to D:\repo (pwd -P reports /d/repo). Contrast: Git Bash's /mnt/d is a false positive — it silently lands in the install root D:\Git\mnt\d. |
| Cygwin form | /cygdrive/d/repo | Fully accepted at the external-command layer; the builtin-side branch is being completed (rubash#850). |
The translation rules are table-driven and context-free (rubash#600): no guessing "does this argument look like a path", no looking at argument position, no checking whether the target exists — paths about to be created also get a legal Win32 form. The shipped PATH-MATRIX runs ~100 probes across the full dialect × execution-layer matrix, and every gap has a filed ticket (WinuxCmd#1244, rubash#850, niu-git#18).
中文原文 · Chinese original
翻译规则是表驱动且与上下文无关的(rubash#600):不猜「这个参数 像不像路径」、不看参数位置、不看目标存在与否——即将创建的路径也得到合法的 Win32 形态。随包的 PATH-MATRIX 以约 100 条探针逐格实跑了方言 × 执行层的全量 矩阵,缺口全部开票在案(WinuxCmd#1244、rubash#850、niu-git#18)。
argv byte-for-byte verbatim: MSYS_NO_PATHCONV does not exist here
Git Bash's MSYS runtime heuristically rewrites path-looking arguments before
spawn: /c becomes C:\, a non-existent /foo expands into
the Git install path, the pattern in git grep "/fn main" gets silently rewritten.
That is why every veteran user has had to learn the MSYS_NO_PATHCONV /
MSYS2_ARG_CONV_EXCL incantations — the existence of an off switch is itself
the official admission of the problem.
niubash has no MSYS runtime; those two environment variables do not exist here and do
not need to. The boundary is a written core invariant — "what must be verbatim is not
converted": commit messages, branch names, quoted operands, the syntax data of
grep/sed/awk/find (patterns, script text, -name-style values),
/cygdrive/…, /dev/*, the environment-variable channel, every
non-path-shaped word — all reach the child process byte-for-byte. The only switch,
__RUBASH_ARGV_DIALECT=legacy, is a documented dialect selector (an emergency
fallback to the old behavior), not a life raft for "heuristics that might guess wrong".
中文原文 · Chinese original
$PWD 恒为 POSIX 形态(如 /d/repo)是配套裁决:这是
nvm 死循环根因(rubash#537)修复的同一决定,生态脚本对 $PWD 的
POSIX 假设因此成立。
uname reporting MSYS_NT: a deliberate compatibility persona
On Windows, uname -s reports MSYS_NT-… (mapped to
MINGW64_NT- and friends in the MSYS2 manner when MSYSTEM is set),
with $OSTYPE=msys and $MACHTYPE=x86_64-pc-msys. This is not an
impersonation of a runtime but a compatibility persona (rubash#154): plenty of
ecosystem scripts use case "$(uname -s)" matching MINGW*|MSYS*|CYGWIN*
as their branching gate, and the persona opens those gates so scripts take their most-tested
paths. Under the label there is no MSYS2 runtime at all: no
msys-2.0.dll, no cygwin1.dll, no POSIX argv conversion layer inside
child processes.
One-line contrast: Git Bash = MSYS2 runtime + real bash; niubash = native engine +
compatibility persona. The persona is disclosed everywhere (--help /
--identity), and RUBASH_IDENTITY=native switches to the honest native
face (uname -s → Windows_NT, OSTYPE=windows).
Honesty section: the write-side gap, tracked in the open
A boundary contract is not zero gaps. The bare-drive-root gap in write-side
commands is being closed: #1230 audited 15 write-side commands, with
mkdir's true write-mode support (#1233) the most urgent. That is the value of a
boundary contract — gaps are enumerable, ticketable and fixable, instead of scattered in the
fog of "sometimes it behaves like Linux, sometimes not". Every cell closed turns another
PATH-MATRIX cell green.