你用 curl 从 Git Bash 里下了个文件到 /tmp/x,命令回显一切正常。紧接着用 Windows 版的 Python 去读 /tmp/x,报「文件不存在」。你回去看 /tmp 目录,文件明明在那儿。两个程序对同一个路径,看到的是两个完全不同的地方。
现象
- Git Bash 里
curl -o /tmp/x ...成功,ls /tmp/x也能看到文件; - 同一个
x,交给 Windows 原生程序(比如python.exe)打开/tmp/x,报FileNotFoundError; - 甚至
ls能列出/tmp/x,但 Python 的open('/tmp/x')找不到。
看起来是「同一个路径」,实际是两个不同的物理位置。
根因
因为在 Git Bash 里,/tmp 不是一个真实的目录,而是一个虚拟映射。
MSYS(Git Bash 的底层)为了模拟 Unix 环境,会拦下对 /、/tmp、/home 这类 Unix 风格路径的访问,把它们重定向到 Windows 上的某个真实目录。比如 Git Bash 的 /tmp 往往对应 Windows 临时目录下的某个子目录,/home/<用户> 对应 Git 安装目录下的某个位置。
但关键在于:这个重定向只发生在 MSYS 自己的程序里。 当 Git Bash 启动一个 Windows 原生程序(python.exe、node.exe、各种 .exe)时,这个进程完全不认识 MSYS 的虚拟路径。它对 /tmp/x 的理解是字面意思——「当前盘符根目录下的 tmp 子目录里的 x」,也就是 C:\tmp\x 或 D:\tmp\x。
于是:
- Git Bash 的
curl把文件写进了「MSYS 的 /tmp」(真实位置是某个 Windows 临时目录); - Windows 的 Python 去读「字面的 /tmp」(真实位置是
C:\tmp)。
两个路径长得一样,指向的目录却不同,文件自然找不到。
同样的道理,MSYS 之外的很多东西都可能不一致:/ 的根、/home、/usr 都是这种虚拟映射。
解决
跨「环境」传文件时,只信显式绝对路径。
不要用 /tmp 这种在两套体系里含义不同的路径做中转。改成用一个明确的 Windows 绝对路径:
# 先在 Git Bash 里下载到显式的 Windows 路径
curl -o 'D:/tools/_upload/x' https://example.com/file
# 再用 Python 读同一个显式路径
python -c "open(r'D:\tools\_upload\x', 'rb').read()"
两边都用 D:\tools\_upload\ 这个物理上唯一的位置,就不会有歧义。
如果你确实需要知道 Git Bash 的 /tmp 到底映射到了哪里,可以用:
cd /tmp && pwd -W
pwd -W 会打印当前目录的 Windows 形式路径,你就能看到它的真实位置。
延伸与预防
这条坑的本质是:「路径字符串」不等于「文件位置」。 同一个字符串,在不同程序、不同环境里,可能被解释到不同的地方。
要判断一个路径会不会「变脸」,看两点:
- 谁来解释它。 是 MSYS 的程序,还是 Windows 原生程序?前者有虚拟映射,后者没有。
- 它是虚拟路径还是物理路径。
/tmp、/home、/这类是 Unix 惯例,在 Windows 上多半是虚拟的;C:\...、D:\...是物理路径,处处一致。
所以一条可靠的实践:跨工具、跨语言、跨环境传文件时,统一用物理绝对路径做中转点,并把它当作双方约定好的「交换区」。 这个交换区可以是固定的一个目录,两边都按它来读写,中间不管经过多少层外壳,指向的都是同一个地方。
这跟前一条「MSYS 路径转换」是一个家族的问题——都是「你以为你写了个路径,其实是环境中某个中间层在替你解释它」。区别只在于:一条是改写,一条是重定向。
一个实用的排查习惯:当你怀疑「两个程序看到的路径不一样」时,让它们分别把「自己理解到的路径」打印出来。比如在 Git Bash 里 echo $PWD 和在 Python 里 print(os.getcwd()),同一时刻对比一下,往往一眼就能看出它们站在不同的根上。别在脑子里推演路径映射,直接让程序把答案打出来。