java ssh登陆主机异常,ssh连接远程主机执行脚本的环境变量问题

本文深入探讨了使用SSH远程执行脚本时遇到的环境变量问题,解析了不同shell模式下配置文件加载的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

近日在使用ssh命令ssh user@remote ~/myscript.sh登陆到远程机器remote上执行脚本时,遇到一个奇怪的问题:

~/myscript.sh: line n: app: command not found

app是一个新安装的程序,安装路径明明已通过/etc/profile配置文件加到环境变量中,但这里为何会找不到?如果直接登陆机器remote并执行~/myscript.sh时,app程序可以找到并顺利执行。但为什么使用了ssh远程执行同样的脚本就出错了呢?两种方式执行脚本到底有何不同?如果你也心存疑问,请跟随我一起来展开分析。

说明,本文所使用的机器是:SUSE Linux Enterprise。

问题定位

这看起来像是环境变量引起的问题,为了证实这一猜想,我在这条命令之前加了一句:which app,来查看app的安装路径。在remote本机上执行脚本时,它会打印出app正确的安装路径。但再次用ssh来执行时,却遇到下面的错误:

which: no app in (/usr/bin:/bin:/usr/sbin:/sbin)

这很奇怪,怎么括号中的环境变量没有了app程序的安装路径?不是已通过/etc/profile设置到PATH中了?再次在脚本中加入echo $PATH并以ssh执行,这才发现,环境变量仍是系统初始化时的结果:

/usr/bin:/bin:/usr/sbin:/sbin

这证明/etc/profile根本没有被调用。为什么?是ssh命令的问题么?

随后我又尝试了将上面的ssh分解成下面两步:

user@local > ssh user@remote

# 先远程登陆到remote上

user@remote> ~

/myscript.sh # 然后在返回的shell中执行脚本

结果竟然成功了。那么ssh以这两种方式执行的命令有何不同?带着这个问题去查询了man ssh:

If command is specified, it is executed on the remote host instead of a login shell.

这说明在指定命令的情况下,命令会在远程主机上执行,返回结果后退出。而未指定时,ssh会直接返回一个登陆的shell。但到这里还是无法理解,直接在远程主机上执行和在返回的登陆shell中执行有什么区别?即使在远程主机上执行不也是通过shell来执行的么?难道是这两种方式使用的shell有什么不同?

暂时还没有头绪,但隐隐感到应该与shell有关。因为我通常使用的是bash,所以又去查询了man bash,才得到了答案。

bash的四种模式

在man page的INVOCATION一节讲述了bash的四种模式,bash会依据这四种模式而选择加载不同的配置文件,而且加载的顺序也有所不同。本文ssh问题的答案就存在于这几种模式当中,所以在我们揭开谜底之前先来分析这些模式。

interactive + login shell

第一种模式是交互式的登陆shell,这里面有两个概念需要解释:interactive和login:

login故名思义,即登陆,login shell是指用户以非图形化界面或者以ssh登陆到机器上时获得的第一个shell,简单些说就是需要输入用户名和密码的shell。因此通常不管以何种方式登陆机器后用户获得的第一个shell就是login shell。

interactive意为交互式,这也很好理解,interactive shell会有一个输入提示符,并且它的标准输入、输出和错误输出都会显示在控制台上。所以一般来说只要是需要用户交互的,即一个命令一个命令的输入的shell都是interactive shell。而如果无需用户交互,它便是non-interactive shell。通常来说如bash script.sh此类执行脚本的命令就会启动一个non-interactive shell,它不需要与用户进行交互,执行完后它便会退出创建的shell。

那么此模式最简单的两个例子为:

用户直接登陆到机器获得的第一个shell

用户使用ssh user@remote获得的shell

加载配置文件

这种模式下,shell首先加载/etc/profile,然后再尝试依次去加载下列三个配置文件之一,一旦找到其中一个便不再接着寻找:

~/.bash_profile

~/.bash_login

~/.profile

下面给出这个加载过程的伪代码:

execute /etc/profile

IF ~/.bash_profile

exists

THEN

execute ~/.bash_profile

ELSE

IF ~/.bash_login exist

THEN

execute ~/.bash_login

ELSE

IF ~/.profile exist

THEN

execute ~/.profile

END

IF

END

IF

END

IF

为了验证这个过程,我们来做一些测试。首先设计每个配置文件的内容如下:

user@remote > cat /etc/profile

echo @ /etc/profile

user@remote > cat ~/.bash_profile

echo @ ~/.bash_profile

user@remote > cat ~/.bash_login

echo @ ~/.bash_login

user@remote > cat ~/.profile

echo @ ~/.profile

然后打开一个login shell,注意,为方便起见,这里使用bash -l命令,它会打开一个login shell,在man bash中可以看到此参数的解释:

-l Make bash act as if it had been invoked as a login shell

进入这个新的login shell,便会得到以下输出:

@ /etc/profile

@ /home/user/.bash_profile

果然与文档一致,bash首先会加载全局的配置文件/etc/profile,然后去查找~/.bash_profile,因为其已经存在,所以剩下的两个文件不再会被查找。

接下来移除~/.bash_profile,启动login shell得到结果如下:

@ /etc/profile

@ /home/user/.bash_login

因为没有了~/.bash_profile的屏蔽,所以~/.bash_login被加载,但最后一个~/.profile仍被忽略。

再次移除~/.bash_login,启动login shell的输出结果为:

@ /etc/profile

@ /home/user/.profile

~/.profile终于熬出头,得见天日。通过以上三个实验,配置文件的加载过程得到了验证,除去/etc/profile首先被加载外,其余三个文件的加载顺序为:~/.bash_profile> ~/.bash_login > ~/.profile,只要找到一个便终止查找。

前面说过,使用ssh也会得到一个login shell,所以如果在另外一台机器上运行ssh user@remote时,也会得到上面一样的结论。

配置文件的意义

那么,为什么bash要弄得这么复杂?每个配置文件存在的意义是什么?

/etc/profile很好理解,它是一个全局的配置文件。后面三个位于用户主目录中的配置文件都针对用户个人,也许你会问为什么要有这么多,只用一个~/.profile不好么?究竟每个文件有什么意义呢?这是个好问题。

Cameron Newham和Bill Rosenblatt在他们的著作《Learning the bash Shell, 2nd Edition》的59页解释了原因:

bash allows two synonyms for .bash_profile: .bash_login, derived from the C shell’s file named .login, and .profile, derived from the Bourne shell and Korn shell files named .profile. Only one of these three is read when you log in. If .bash_profile doesn’t exist in your home directory, then bash will look for .bash_login. If that doesn’t exist it will look for .profile.

One advantage of bash’s ability to look for either synonym is that you can retain your .profile if you have been using the Bourne shell. If you need to add bash-specific commands, you can put them in .bash_profile followed by the command source .profile. When you log in, all the bash-specific commands will be executed and bash will source .profile, executing the remaining commands. If you decide to switch to using the Bourne shell you don’t have to modify your existing files. A similar approach was intended for .bash_login and the C shell .login, but due to differences in the basic syntax of the shells, this is not a good idea.

原来一切都是为了兼容,这么设计是为了更好的应付在不同shell之间切换的场景。因为bash完全兼容Bourne shell,所以.bash_profile和.profile可以很好的处理bash和Bourne shell之间的切换。但是由于C shell和bash之间的基本语法存在着差异,作者认为引入.bash_login并不是个好主意。所以由此我们可以得出这样的最佳实践:

应该尽量杜绝使用.bash_login,如果已经创建,那么需要创建.bash_profile来屏蔽它被调用

.bash_profile适合放置bash的专属命令,可以在其最后读取.profile,如此一来,便可以很好的在Bourne shell和bash之间切换了

non-interactive + login shell

第二种模式的shell为non-interactive login shell,即非交互式的登陆shell,这种是不太常见的情况。一种创建此shell的方法为:bash -l script.sh,前面提到过-l参数是将shell作为一个login shell启动,而执行脚本又使它为non-interactive shell。

对于这种类型的shell,配置文件的加载与第一种完全一样,在此不再赘述。

interactive + non-login shell

第三种模式为交互式的非登陆shell,这种模式最常见的情况为在一个已有shell中运行bash,此时会打开一个交互式的shell,而因为不再需要登陆,因此不是login shell。

加载配置文件

对于此种情况,启动shell时会去查找并加载/etc/bash.bashrc和~/.bashrc文件。

为了进行验证,与第一种模式一样,设计各配置文件内容如下:

user@remote > cat /etc/bash.bashrc

发表于 2019-08-20 11:00

阅读 ( 1578 )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值