SSH连接远程服务器直接执行command时PATH设置不全的一种解决方案


git作为DVCS的强大功能之一就是可以不用中心server节点,直接在developers之间的host同步源码。这个好处有很多,比如可以想到的一个比较明显的好处就是:可以在code尚未ready的情况下,随意在host之间同步,方便协作开发。(试想一下,如果是传统的CVCS,比如SVN,在代码尚未ready的时候,将代码就提交的中心repo,那简直就是噩梦。不幸的是,如果你如果不借助诸如rsync之类的外部工具,你几乎根本没办法绕开中心仓库,因为他是链接dev之间的一座桥)

今天折腾了一下在自己的desktop和laptop之间同步code的具体设置,没想到还真没那么顺利。 - Desktop:git clone 了remote repo A, 并做了一些修改,但code尚未完全测试,不适合push回到remote repo。 - Laptop:同样git clone 了remote repo A,这时希望获得desktop的所有修改,在Laptop上继续进行开发。于是执行了下面的命令:

git remote add desktop user@desktop.domain:/desktop/repo/path/.git  
git checkout -b from-desktop # create a new branch to receive the modification from desktop  
git pull desktop from-desktop # fetch from the desktop andmerge it to the branch from-desktop
  • 杯具的是执行最后一步时,爆出了git-upload-pack command not found这种错误。所以git pull也根本没有成功。
  • 从提示上看是找不到git-upload-pack, 估计是PATH中没有git-upload-pack所在的路径。因为git是公司统一的标准安装路径,用which git-upload-pack 查看了一下,在一个特殊的路径/company/set/path下。
  • 上面的git remote add命令中没有指定git使用的协议,默认使用的就是ssh了。这样分析说明问题出在ssh远程执行命令上了。
  • 从Laptop ssh到desktop上执行echo $PATH, 发现PATH中正常包括/company/set/path这个路径。
  • 退出来之后,再通过ssh username@desktop.domain 'echo $PATH‘查看PATH变量,这时果然就不包括/company/set/path了。

问题基本上定位到了,就是因为这里的差异,但是为什么会有这样的差异呢? 在Desktop上的.bash_profile设置非常简单,就是下面这些,

if [ -f $HOME/.profile ]; then  
        . $HOME/.profile  
fi  

if [ -f $HOME/.bashrc ]; then  
        . $HOME/.bashrc  
fi

两者理论上都执行了这个配置文件,我的$HOME下只有.bashrc的配置(对PATH的若干设置和export就在.bashrc中,其中就包括/company/set/path),所以两者应该走了同样的路径,没道理,一个路径中有,一个路径中没有啊。

找了半天,终于在.bashrc的第一行注意到这一句

# If not running interactively, don't do anything  
[ -z "PS1" ] && return

恍然大悟,罪魁祸首啊!

  • Linux系统中一种常用的判断是否是交互shell的方式就是通过PS1变量,虽然还有其他的方式,不过现在.bashrc中是通过PS1来判断是否为interactive mode。
  • 通过ssh协议登陆到Desktop上时,已经在Laptop上开启了一个tty终端,属于interactive mode, 所以上面的那行不会进入return逻辑,整个.bashrc会被执行完,于是PATH中包括了所有设置过的关键路径。(N多export的PATH都放在了.bashrc的末尾)
  • 但是ssh username@desktop.domain 'command'这种是典型的non-interactive shell,所以虽然也执行了.bashrc文件,但是在一开始就直接进入了return逻辑返回了,所以文件底部的对PATH的export根本没有生效。于是,就有了开始杯具。
  • 找到根本问题了,解决方案就有了,将必要的export PATH的声明,全部移到[ -z "PS1" ] && return 之前。保证在non-interactive mode下,PATH的设置也都会生效。
  • 再次从Laptop上执行git pull desktop from-desktop,所有修改被成功merge到Laptop的from-desktop分支上,问题解决!