Oh, that is tricky and needs a bit of an essay. Sorry. I’ll restrict most of it to just Cygwin-derived shells, which covers the vast majority of the normal Windows shell use cases today. The minority use cases I talk about at the bottom.
Cygwin has a DLL called
cygwin1.dll. MSYS2 has a DLL called
msys-2.0.dll which is similar so I’ll ignore it. DLLs behave somewhat similarly to
.so shared libraries … the TEXT (code) and DATA segments are mapped into the virtual address space of the process they are linked to (analog:
ld.so) or loaded into (analog:
dlopen). But DLLs can have well-defined entry and exit functions that automatically get called whenever they are loaded into a process. What
cygwin1.dll does is load a shared memory object when it is attached to any process, so that all processes linked with
cygwin1.dll see the same shared memory object.
All the normal GNU binaries like
dash.exe (the POSIX shell),
make.exe and all the GNU autoconf tools are linked with
cygwin1.dll. And these GNU binaries are compiled using normal POSIX C code like
fopen is a good example.
fopen("/etc/passwd", "r") by consulting
cygwin1.dll/../etc/fstab to find the mapping between
/etc/passwd and the Windows file system; for speed the mapping is stashed into the shared memory object. The really important point is that each
cygwin1.dll has its own shared memory object which gives its own file system, its own POSIX locks, and so on.
On my machine:
- Cygwin64 Terminal has
/etc/passwd -> Z:\cygwin64\etc\passwd
- MSYS2 Terminal has
/etc/passwd -> Z:\msys64\etc\passwd
- Git Bash’s embedded MSYS2 has
/etc/passwd -> C:\Program Files\Git\etc\passwd
- Diskuv OCaml’s embedded MSYS2 has
/etc/passwd -> C:\Users\beckf\AppData\Local\Programs\DiskuvOCaml\tools\MSYS2\etc\passwd
- vcpkg’s embedded MSYS2 has
/etc/passwd -> Z:\source\cpkgs\vcpkg\downloads\tools\msys2\9a1ec3f33446b195\etc\passwd
cygwin1.dll also proxies the
getenv("PATH") function. It takes all of the PATH entries from Windows and translates them into the
/ Unix-y file system.
/ reverse mappings are relative to the specific
cygwin1.dll. So if I were in Cygwin64 Terminal I would see:
Z:\cygwin64\etc\passwd -> /etc/passwd
Z:\msys64\etc\passwd -> /cygdrive/z/msys64/etc/passwd
C:\Program Files\Git\etc\passwd -> /cygdrive/c/Program Files/Git/etc/passwd
- and so on
And if I were in MSYS2 Terminal I would see the same pattern (except it doesn’t use
Z:\cygwin64\etc\passwd -> /z/cygwin64/etc/passwd
Z:\msys64\etc\passwd -> /etc/passwd
C:\Program Files\Git\etc\passwd -> /c/Program Files/Git/etc/passwd
- and so on
So there is no loss of fidelity moving between PATHs on Windows and “Unix”.
Finally, if you understood everything I wrote above, can you predict what happens when:
Your Windows PATH is a seemingly reasonable:
You are inside the MSYS2 Terminal shell
You run a script with a shebang:
Which bash will be used? MSYS2 Terminal’s or Git’s?
How about if you run the following?
Minority use cases: WSL2 shells
Very very annoyingly if I type
bash on the Command Prompt I immediately enter WSL2 Ubuntu Linux. I don’t know when that was introduced, and I don’t know the mechanism how that works. All I know is that I’ve had shell scripts magically invoke Ubuntu Linux from Cygwin-derived shells.
Yet another complexifier.
The problem of invoking the correct shell is solved as long as every package uses
#!/bin/sh rather than
#!/usr/bin/env sh. Good luck trying to get that to happen, especially retroactively! Or we can do what the POSIX spec suggests … let one place (the installer) determine the correct shebang. And as a bonus, nixos can magically start working as well.
As a stop-gap, I have wrappers around common OCaml tools (so far
opam.exe) that constructs sanitized PATHs for Diskuv OCaml native Windows users. See crossplatform-functions.sh and with_dkml.ml. Those hacks are the only reason why this isn’t high priority for Windows.