Windows OpenSSH Server 配置 Git Bash 为默认 Shell
有些错误信息读起来像是一句存在主义的判词。
| |
你明明在登录,对方也明明在等你,但中间那个传话的人把你的名字念错了——于是门禁系统礼貌而坚定地将你拒之门外。
这是在 Windows OpenSSH Server 上将默认 Shell 替换为 Git Bash 之后遇到的情形。交互式登录一切正常,推门进去茶水伺候;但凡想远程执行一条命令——哪怕只是 echo OK 这样卑微的请求——便立刻碰壁。仿佛一座宅子,住进去没问题,往里面递个纸条却要吃闭门羹。
症状
通过 SSH 交互式登录目标 Windows 主机正常,但执行非交互式命令时报错:
| |
修改 Shell 路径后又出现变体:
| |
前一个是身份认不出,后一个是命令认不出。病灶不同,病根在同一处。
病理
Windows OpenSSH Server 通过注册表控制默认 Shell 的行为,关键路径是:
| |
当你远程执行 ssh host "echo OK" 时,sshd 在服务端的实际调用是:
| |
问题的核心在于一个极易忽略的事实:Windows OpenSSH 将 DefaultShellCommandOption 的整个值作为单个参数传递给 Shell。
也就是说,注册表里写的 --login -i,到了 bash 手里不是两个参数 --login 和 -i,而是一个浑然一体的 "--login -i"。bash 面对这个四不像,只能摊手。
这大概是 Unix 世界与 Windows 世界之间最常见的文化冲突之一:对"空格"的理解不同。一个认为那是分隔符,另一个认为那是字符串的一部分。
排查三层
第一层:两个 bash,选哪个
Git for Windows 提供两个 bash 入口:
| 路径 | 说明 |
|---|---|
C:\Program Files\Git\usr\bin\bash.exe | 原生 MSYS2 bash 二进制 |
C:\Program Files\Git\bin\bash.exe | 启动器,负责环境初始化 |
前者是裸奔的 bash,缺少 MSYS2 的环境准备。应当选择后者。
第二层:参数的合并之灾
既然 OpenSSH 会把 DefaultShellCommandOption 整体打包:
--login -i→ bash 收到"--login -i"→invalid option--login -c→ bash 收到"--login -c"→ 同样失败-c→ bash 收到"-c"→ 正确识别,将后续内容当作命令字符串执行
结论:DefaultShellCommandOption 只能放一个不含空格的参数。
如需登录 Shell 的环境变量加载行为,不能在此处解决,应在 .bashrc 中 source ~/.bash_profile。
第三层:没有 -c 的后果
将 DefaultShellCommandOption 设为 --login(去掉 -i),bash 不再抱怨非法选项,但新的报错随之而来:
| |
没有 -c,bash 把 echo OK 当成了一个脚本文件的路径去寻找。找不到,于是报告"文件不存在"。一个命令被当成了地址——颇有几分黑色幽默。
修复
在目标 Windows 主机上执行以下 PowerShell 命令:
| |
验证:
| |
附记
几点容易遗忘的细节:
- 修改注册表后必须重启 sshd 服务,否则配置不会生效。
- 在 Git Bash 环境下直接使用
reg命令操作注册表,可能因 MSYS2 的路径自动转换而报语法错误。用powershell -Command包裹更为稳妥。 - 为 Administrators 组用户配置免密登录时,公钥应写入
C:\ProgramData\ssh\administrators_authorized_keys,而非用户目录下的~/.ssh/authorized_keys。这是 Windows OpenSSH 对管理员账户的特殊处理,初见之下颇觉不合常理,但查阅文档后会发现它自有逻辑——就像许多看似任性的规矩一样。
三个参数,折腾了三层。到最后发现,答案只是一个孤零零的 -c。技术排查常常如此:拨开层层迷雾,底下藏着的不过是一粒沙。