环境变量与 Shell¶
对于 SSH 登录、远程执行命令、本机登录等不同场景,环境变量可能不同,这常常引起许多困惑。本篇文章根据相关资料总结一下环境变量的加载过程。
Shell 的模式¶
Quote
login/non-login shell¶
login/non-login shell 的区别在于是否需要输入用户名密码。让我们举一些例子:
- 从控制台登录系统,需要输入用户名密码,这是 login shell。
- 使用
su
切换用户,需要输入指定用户的密码,这是 login shell。 - 使用
ssh
登录远程系统,需要输入密码,这是 login shell。 - 使用
bash
启动新的 shell,这是 non-login shell。 - 在图形界面下打开终端(如 konsole、gnome-terminal),这是 non-login shell。因为你已经通过如 GDM、KDM、LightDM 等图形化登录界面登录了系统。
可以看出,要启动 non-login shell,首先要有已经登录的用户。
interactive/non-interactive shell¶
interactive/non-interactive shell 的区别是该 Shell 是否接受用户输入。让我们举一些例子:
- 从控制台登录系统,输入命令,这是 interactive shell。
- 使用
bash -c 'echo hello'
执行命令,这是 non-interactive shell。 - 使用
bash
启动新的 shell,这是 interactive shell。 - 使用
bash
启动新的 shell 执行脚本,这是 non-interactive shell。
值得注意的是,即使脚本中使用 read
等命令向用户请求输入,也是 non-interactive shell。因为这并不是 Shell 本身的交互,而是脚本中命令的交互。
两者的组合¶
bash
选项可以控制 shell 的模式:
-l
:login shell(虽然使用bash -l
还是不需要输入用户名密码,但可以看到环境变量确实按 login shell 加载了)-c
:non-interactive shell
可以使用下列命令检查当前的 shell 模式:
echo $-
:- 包含
i
表示 interactive shell - 不包含
i
表示 non-interactive shell
- 包含
shopt login_shell
:login_shell
为on
表示 login shelllogin_shell
为off
表示 non-login shell
使用 SSH 远程执行命令时,OpenSSH 将启动一个 non-login、non-interactive shell。
Shell 启动时加载的文件¶
/etc/profile
被认为是只在用户登录(login shell)时执行一次的文件。这导致我们使用 SSH 远程执行命令或使用 MPI 通过 SSH 启动进程时(为 non-login non-interactive shell),放置在 /etc/profile
中的相关环境变量均不会生效。对于不同的 Shell,应当查找其对应的启动文件,以确保环境变量的正确加载。
Bash¶
Quote
模式 | 文件 |
---|---|
login | /etc/profile ~/.bash_profile ~/.bash_login ~/.profile |
interactive non-login | ~/.bashrc |
non-interactive non-login | 环境变量 BASH_ENV 指定的文件 |
remote non-interactive | ~/.bashrc |
关于 Bash 远程执行命令时加载文件的进一步探讨
/* If we were run by sshd or we think we were run by rshd, execute
~/.bashrc if we are a top-level shell. */
#if 1 /* TAG:bash-5.3 */
if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
#else
if (isnetconn (fileno (stdin) && shell_level < 2)
#endif
/* Define this if you want bash to try to check whether it's being run by
sshd and source the .bashrc if so (like the rshd behavior). This checks
for the presence of SSH_CLIENT or SSH2_CLIENT in the initial environment,
which can be fooled under certain not-uncommon circumstances. */
/* #define SSH_SOURCE_BASHRC */
SSH_SOURCE_BASHRC
默认为 undefined。那唯一的条件就是,如果stdin 为网络连接,则会加载 bashrc。
判断网络连接的方式为,测试下 getpeername 会不会报错:bash/netconn.c - GitHub
Fish¶
Fish 不会读取 /etc/profile
- Configuration snippets:
~/.config/fish/conf.d/
/etc/fish/conf.d/
~/.local/share/fish
/usr/share/fish/vendor_conf.d
/usr/local/share/fish/vendor_conf.d
- System-wide configuration files:
/etc/fish/config.fish
- User configuration:
~/.config/fish/config.fish
Fish 文档说明,不论以何种方式启动,都会按照上述顺序加载配置文件。但实际操作时,发现 non-interactive 时似乎不会加载 snippets。总之,对于 Fish,要保证所有情况下加载,应当将配置直接写入 /etc/fish/config.fish
。
Zsh¶
/etc/zsh/zshenv
~/.zshenv
/etc/zsh/zprofile
(if login shell)~/.zprofile
(if login shell)/etc/zsh/zshrc
(if interactive)~/.zshrc
(if interactive)/etc/zsh/zlogin
(if login shell)~/.zlogin
(if login shell)
对于 Zsh,要保证所有情况下加载,应当将配置直接写入 /etc/zsh/zshrc
。
远程执行命令的方式¶
远程执行命令有非常多种方式,不一定涉及 Shell。
-
SSH 总是通过用户的 Login Shell 执行命令,但该 Shell 的状态不一定能够是 login 的。比如,执行一个不存在的命令,会由 Login Shell 报告错误信息:
-
Slurm 远程执行则不涉及 Shell。启动不存在的文件,可以看到是由
execve()
报告错误信息: