为什么通过SSH远程执行命令时获取的环境变量比手动执行时少?

281

我有一个命令,如果我ssh登录到一台机器并运行它,那么它可以正常运行,但是当我尝试使用远程ssh命令运行它时则失败了:

ssh user@IP <command>

使用这两种方法比较“env”命令的输出结果会得到不同的环境变量。当我手动登录到机器上并运行“env”命令时,获取到的环境变量要比执行以下命令时少:

ssh user@IP "env"

有什么想法吗?


57
为什么这个问题被关闭了,理由是什么? - jottr
14
可能是因为它与编程无关。 应该将其移至Super User而不是关闭。 - Daniel H
13
bash 不是一种脚本语言? - Dan Nissenbaum
1
在Debian 8中,由于某种原因,我不得不在/etc/passwd中将shell更改为bash。即使重新配置dash以使/bin/sh指向bash也没有帮助。 - user1050755
1
可能是您的 sshd 配置了 UsePAM no。如果是这样,那么通常负责自动获取 /etc/environment 的 "PAM configuration for sshd" (/etc/pam.d/sshd) 将会失效。... 设置 UsePAM yes(并重新启动 sshd 以应用此更改)现在应该为 ssh 登录 shell 提供来自 /etc/environmentexport。... 根据 https://man7.org/linux/man-pages/man5/sshd_config.5.html -> UsePAM 文档,根据您的需求,建议还设置以下内容:KbdInteractiveAuthentication noPasswordAuthentication no - Abdull
6个回答

199

不同类型的shell有所区别。SSH命令执行shell是非交互式shell,而您普通的shell则是登录shell或交互式shell。以下来自man bash的描述:

       登录shell是其第一个参数的第一个字符为 - ,或使用--login选项启动的shell。
交互式shell是没有非选项参数和没有使用-c选项启动的shell,并且其标准输入和错误输出都连接到终端(由isatty(3)确定),或使用-i选项启动的。如果bash是交互式的,则设置PS1和$-包括i,允许shell脚本或启动文件测试此状态。
下面的段落描述了bash如何执行其启动文件。如果存在任何文件但无法读取,则bash会报告错误。在扩展节中,文件名中的波浪号将扩展为下面所述。
当bash作为交互式登录shell或使用--login选项启动的非交互式shell时,它首先从文件/etc/profile中读取并执行命令,如果该文件存在。读取该文件后,它查找~/.bash_profile、~/.bash_login和~/.profile,按顺序读取并执行第一个存在且可读的文件。当启动shell时可以使用--noprofile选项以禁止这种行为。
当登录shell退出时,如果存在~/.bash_logout文件,则bash会读取并执行其中的命令。
当启动交互式shell而不是登录shell时,如果存在~/.bashrc文件,则bash会从中读取并执行命令。可以使用--norc选项禁止此操作。--rcfile文件选项将强制bash从文件中读取并执行命令,而不是从~/.bashrc中读取。当Bash非交互式地启动时,比如运行一个shell脚本,它会在环境变量中查找变量BASH_ENV的值,并扩展其值(如果在环境变量中存在),然后使用扩展后的值作为要读取和执行的文件名。 Bash的行为就像执行了以下命令:
`if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi`
但是不使用PATH变量来搜索文件名。

13
非常好的答案,这正是问题所在。需要的环境变量位于/etc/bashrc中,在非交互模式下不会载入。 将它们移动到/etc/profile中解决了问题。 非常感谢! - Tom Feiner
2
这个答案只是一个部分解决方案。以下是更多信息:在各种被引用的文件中添加不同的环境变量(例如,export SOURCED_SYSTEM_ETC_BASHRC):/etc/profile、etc/bashrc、/.profile、/.bash_profile、~/bashrc。然后在Jenkins输出中查找该唯一变量。在我的情况下,我更新了/etc/bashrc以包含“export SOURCED_SYSTEM_ETC_BASHRC=yes”,并且该变量出现在节点的Jenkins日志中。因此,在我的情况下,要为jenkins从机设置环境变量,它们必须放在/etc/bashrc中。Jenkins ssh登录引用/etc/bashrc。 - Coder Roadie
62
这是一大段文字,我认为值得强调的是ssh user@host "bash --login -c 'command arg1 ...'"将使远程shell设置登录环境。你引用的那段内容确实提到了--login,但很容易被忽略。 - codebeard
1
谢谢您的帖子,我使用ssh <ssh选项> <IP> bash --login my_script.sh在远程机器上运行脚本,效果很好,并成功地使用了本地环境变量,如JAVA_HOME - markc
2
在一些UNIX系统(如AIX)中,将环境变量添加到/etc/profile中并不能起作用,但是将其添加到/etc/environment中可以。 - R.Liu
显示剩余2条评论

129

在运行命令之前先进行配置文件的资源引用怎么样?

ssh user@host "source /etc/profile; /path/script.sh"

你可能认为最好将其更改为~/.bash_profile~/.bashrc或其他文件。

(参考 这里 (linuxquestions.org))


1
遇到了这个问题,这个解决方案非常有效。 - Sam Becker
12
需要总是额外输入代码以源化环境真是荒谬! - Michael
4
通常情况下很少重复输入这些类型的内容,通常会在脚本中进行,因此不管有多少“额外的代码”,只要它能工作就可以了 :tm: - Ian Vaughan
2
如果我同时源化 /etc/profile 和 ~/.bash_profile(以获取用户添加到路径中的内容),它可以工作,但这很丑陋。肯定有更简单的方法告诉命令(在我的情况下是 xterm)使用“交互式”登录,并在远程机器上获得完整的用户特定路径? - Jess
1
确定需要引用哪个 bashrcbash_profile 脚本可能很困难。相反,让 bash 自己处理这个问题: ssh user@host "bash --login -c 'command arg1 ...'" - codebeard
显示剩余5条评论

118

当远程运行ssh命令时,Shell环境不会加载。 您可以编辑ssh环境文件:

vi ~/.ssh/environment

它的格式是:

VAR1=VALUE1
VAR2=VALUE2

还要检查sshd配置中的PermitUserEnvironment=yes选项。


1
完美!如果可以的话,加100分 :) - Dexter
VisualGDB在运行时出现了错误“bash:gcc:command not found”,而这个解决方案纠正了这个问题。 - KalenGi
这是完美的答案! - Jirapong
1
@pg2455,如果您无法在服务器上配置sshd(或不想为所有用户启用它),仍然可以编辑您的用户环境。 - dpedro
您好,我可以问一下sshd文件在哪里吗? - Tengerye
显示剩余3条评论

77

我遇到了类似的问题,但最终我发现只需修改~/.bashrc文件即可解决问题。

然而,在Ubuntu中,我需要注释掉停止处理~/.bashrc的那一行:

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

13
或者,你可以将所有的“非交互式”代码都放在那行代码的上方。 - machineghost
1
美丽的,节省了我很多时间 :) 谢谢 - Lance
谢谢。只是补充一下,带有返回语句的文件可以在/etc/bash.bashrc找到。 - adarshr
有没有任何方法在不改变服务器代码的情况下绕过它? - Yoni
1
我花了很多时间试图理解为什么我的脚本不起作用。谁想到把这些行放在.bashrc里是个好主意? - Sharcoux

4
我发现解决这个问题的简单方法是在我尝试在目标系统上运行的script.sh文件的顶部添加source /etc/profile。在这些系统上,这会导致script.sh所需的环境变量被配置为从登录shell运行时的环境变量。
在之前的回答中,建议使用~/.bashr_profile等。我没有花太多时间研究,但是问题在于,如果您在目标系统上ssh到不同的用户而不是从您登录的源系统的shell,则似乎会导致使用源系统用户名的~。

完美!这是一个很好的一行代码解决方案。 - corpico

4

只需在~/.bashrc中非交互式shell的检查之前导出您想要的环境变量即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接