手动SSH与脚本内SSH调用为何使用不同的zsh?
我有一个deploy.sh文件,希望从本地MacBook运行它,通过ssh连接到远程Ubuntu 22服务器并在那里执行一些命令,示例如下:
#!/opt/homebrew/bin/zsh main() { scp \ on_server.sh \ # other files \ "$remote_location:/correct/location" ssh $remote_location "cd correct/location; echo $SHELL; ./on_server.sh" } main "$@"
on_server.sh文件包含几个使用pnpm和pm2的命令:
#!/usr/bin/zsh pnpm i pnpm build pm2 restart app_name
如果我手动ssh登录服务器,cd到对应目录并执行这些pnpm和pm2命令,一切正常。但当deploy.sh执行看似相同的操作时,我会收到pnpm和pm2命令未定义的错误。
不过我注意到两种情况下的$SHELL值不同:
手动ssh时:
ssh <remote_location> ... echo $SHELL /usr/bin/zsh
而上述on_server.sh中echo $SHELL的输出为:
/bin/zsh
我认为$SHELL的差异是命令执行失败的原因,想知道为何$SHELL值会存在这种差异?
1. $SHELL差异的本质
你看到的$SHELL差异,核心是交互式登录shell与非交互式非登录shell的区别:
- 手动ssh登录时,系统启动的是交互式登录shell,此时会读取你用户目录下的配置文件(比如
~/.zprofile、~/.zshrc),$SHELL变量会指向/etc/passwd中为你配置的默认shell路径(也就是/usr/bin/zsh)。 - 通过
ssh remote "command"执行命令时,启动的是非交互式非登录shell,此时$SHELL会继承ssh会话的默认环境值(这里是/bin/zsh),更关键的是:这种模式下不会加载~/.zshrc或~/.zprofile这类用户配置文件。
2. 命令找不到的核心原因
pnpm和pm2这类全局安装的工具,其执行路径通常是在~/.zshrc或~/.zprofile中添加到PATH环境变量里的。非交互式shell不加载这些配置,导致PATH中没有包含工具的路径,自然会报“命令未定义”的错误。
3. 修复方案
可以通过以下几种方式解决:
方案一:在远程命令中显式加载配置文件
修改deploy.sh中的ssh命令,强制加载zsh的配置文件:ssh $remote_location "cd correct/location; source ~/.zshrc; ./on_server.sh"如果配置写在
~/.zprofile里,就替换成source ~/.zprofile。方案二:在
on_server.sh中使用工具的绝对路径
先手动登录服务器,用which pnpm和which pm2拿到工具的绝对路径,然后修改on_server.sh:#!/usr/bin/zsh /path/to/pnpm i /path/to/pnpm build /path/to/pm2 restart app_name方案三:让ssh以登录shell模式执行命令
通过zsh的-l参数强制启动登录shell,自动加载配置文件:ssh $remote_location "zsh -l -c 'cd correct/location; ./on_server.sh'"
内容的提问来源于stack exchange,提问作者Jackson H

