Windows OpenSSH Server 配置 Git Bash 为默认 Shell

有些错误信息读起来像是一句存在主义的判词。

1
/usr/bin/bash: --login -i: invalid option

你明明在登录,对方也明明在等你,但中间那个传话的人把你的名字念错了——于是门禁系统礼貌而坚定地将你拒之门外。

这是在 Windows OpenSSH Server 上将默认 Shell 替换为 Git Bash 之后遇到的情形。交互式登录一切正常,推门进去茶水伺候;但凡想远程执行一条命令——哪怕只是 echo OK 这样卑微的请求——便立刻碰壁。仿佛一座宅子,住进去没问题,往里面递个纸条却要吃闭门羹。

症状

通过 SSH 交互式登录目标 Windows 主机正常,但执行非交互式命令时报错:

1
/usr/bin/bash: --login -i: invalid option

修改 Shell 路径后又出现变体:

1
/usr/bin/bash: echo OK: No such file or directory

前一个是身份认不出,后一个是命令认不出。病灶不同,病根在同一处。

病理

Windows OpenSSH Server 通过注册表控制默认 Shell 的行为,关键路径是:

1
2
3
HKLM\SOFTWARE\OpenSSH
├── DefaultShell              → Shell 可执行文件路径
├── DefaultShellCommandOption → 传递给 Shell 的参数

当你远程执行 ssh host "echo OK" 时,sshd 在服务端的实际调用是:

1
<DefaultShell> <DefaultShellCommandOption> "echo OK"

问题的核心在于一个极易忽略的事实: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 的环境变量加载行为,不能在此处解决,应在 .bashrcsource ~/.bash_profile

第三层:没有 -c 的后果

DefaultShellCommandOption 设为 --login(去掉 -i),bash 不再抱怨非法选项,但新的报错随之而来:

1
/usr/bin/bash: echo OK: No such file or directory

没有 -c,bash 把 echo OK 当成了一个脚本文件的路径去寻找。找不到,于是报告"文件不存在"。一个命令被当成了地址——颇有几分黑色幽默。

修复

在目标 Windows 主机上执行以下 PowerShell 命令:

1
2
3
4
5
6
7
8
# 设置默认 Shell 为 Git Bash 启动器
Set-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Program Files\Git\bin\bash.exe'

# 设置命令选项为 -c(不能附加其他参数)
Set-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShellCommandOption -Value '-c'

# 重启 sshd 服务使配置生效
Restart-Service sshd

验证:

1
2
3
4
ssh <目标主机> "echo OK && hostname"
# 预期输出:
# OK
# <主机名>

附记

几点容易遗忘的细节:

  • 修改注册表后必须重启 sshd 服务,否则配置不会生效。
  • 在 Git Bash 环境下直接使用 reg 命令操作注册表,可能因 MSYS2 的路径自动转换而报语法错误。用 powershell -Command 包裹更为稳妥。
  • 为 Administrators 组用户配置免密登录时,公钥应写入 C:\ProgramData\ssh\administrators_authorized_keys,而非用户目录下的 ~/.ssh/authorized_keys。这是 Windows OpenSSH 对管理员账户的特殊处理,初见之下颇觉不合常理,但查阅文档后会发现它自有逻辑——就像许多看似任性的规矩一样。

三个参数,折腾了三层。到最后发现,答案只是一个孤零零的 -c。技术排查常常如此:拨开层层迷雾,底下藏着的不过是一粒沙。