(本文基于5.1-6ubuntu1.1
版本的bash
)
bash
在被调用时会读取哪些启动文件?要回答这个问题,首先要弄清楚两个概念:login shell和interactive shell。
login shell
login shell是指这样的shell:
- 第一个命令行参数(进程名)以
-
开头,也就是满足argv[0][0] == '-'
- 或者 设置了
--login
/-l
选项
对于条件1,有一个例子:当执行su - [USER_NAME]
时,su
命令会启动一个login shell,argv[0][0]
会被设置为-
。su
手册中写到:
-, -l, --login
Start the shell as a login shell with an environment similar to a real login:
• clears all the environment variables except TERM and variables specified by --whitelist-environment
• initializes the environment variables HOME, SHELL, USER, LOGNAME, and PATH
• changes to the target user’s home directory
• sets argv[0] of the shell to '-' in order to make the shell a login shell
不满足login shell条件的即为non-login shell。
interactive shell
interactive shell是指这样的shell:
- 满足以下条件
- 没有非选项参数(如果设置了
-s
选项则可以有非选项参数(non-option arguments)) - 并且 没有设置
-c
选项 - 并且 标准输入和标准错误都被连接到终端
- 没有非选项参数(如果设置了
- 或者 设置了
-i
选项
对于一个interactive shell,PS1
环境变量会被设置,同时$-
中会含有i
选项:
$ echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$
$ echo $-
himBHs
interactive shell,顾名思义,指可交互的shell。所以像是创建子进程执行一个脚本(bash ./foo.sh
)或是执行一条命令(bash -c 'ls'
)时,创建出来的bash
进程就显然不是可交互的,因此不属于interactive shell。除此之外,bash
还把重定向了标准输入或者标准错误的bash
排除在了interactive shell之外。所以对于bash 2>/dev/null
这种情况,尽管实际上是可交互的,但仍然不属于interactive shell。下面这些命令创建的bash
进程都不属于interactive shell:
$ bash ./foo.sh
$ bash -c 'ls'
$ bash < foo.sh
$ bash 2>/dev/null
然而,bash
提供了一个选项-i
,可以强行将一个bash
设置为interactive shell,上面提供的4个例子中,只要加上了-i
选项,它们就全部变成了interactive shell。
所以说,判断一个bash
是否为interactive shell,不能从表现上看,还是要看它在被调用时设置了哪些参数。
在代码层面上,interactive shell的判断逻辑如下:
/* First, let the outside world know about our interactive status.
A shell is interactive if the `-i' flag was given, or if all of
the following conditions are met:
no -c command
no arguments remaining or the -s flag given
standard input is a terminal
standard error is a terminal
Refer to Posix.2, the description of the `sh' utility. */
if (forced_interactive || /* -i flag */
(!command_execution_string && /* No -c command and ... */
wordexp_only == 0 && /* No --wordexp and ... */
((arg_index == argc) || /* no remaining args or... */
read_from_stdin) && /* -s flag with args, and */
isatty (fileno (stdin)) && /* Input is a terminal and */
isatty (fileno (stderr)))) /* error output is a terminal. */
init_interactive ();
else
init_noninteractive ();
不满足interactive shell条件的即为non-interactive shell。
不同情况下读取的启动文件
在了解了login shell和interactive shell的概念后,我们就可以看看在不同情况下bash
会读取哪些启动文件了。
- login && (interactive || non-interactive) shell
- 读取
/etc/profile
- 读取
~/.bash_profile
、~/.bash_login
、~/.profile
中存在且可读的第一个
- 读取
- non-login && interactive shell
- 读取
/etc/bash.bashrc
- 读取
~/.bashrc
- 读取
- non-login && non-interactive shell
- 读取
BASH_ENV
环境变量中保存的文件路径,如果存在的话
- 读取
需要注意的是,尽管login shell不会直接读取/etc/bash.bashrc
或是~/.bashrc
,但是通常会在满足条件时,通过/etc/profile
和~/.bash_profile
/~/.bash_login
/~/.profile
间接读取这两个文件:
# /etc/profile
if [ "${PS1-}" ]; then
if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then
# The file bash.bashrc already sets the default PS1.
# PS1='\h:\w\$ '
if [ -f /etc/bash.bashrc ]; then
. /etc/bash.bashrc
fi
else
if [ "$(id -u)" -eq 0 ]; then
PS1='# '
else
PS1='$ '
fi
fi
fi
# ~/.profile
if [ -n "$BASH_VERSION" ]; then
# include .bashrc if it exists
if [ -f "$HOME/.bashrc" ]; then
. "$HOME/.bashrc"
fi
fi
特殊情况下读取的启动文件
以sh
的名字调用时
在这种情况下,bash
会模仿sh
的行为:
- login && (interactive || non-interactive) shell
- 读取
/etc/profile
- 读取
~/.profile
- 读取
- non-login && interactive shell
- 读取
ENV
环境变量中保存的文件路径
- 读取
- non-login && non-interactive shell
- 不读取启动文件
以posix
模式调用时
如果bash
调用时设置了--posix
参数,那么在interactive shell的情况下,bash
会尝试从ENV
环境变量中获取启动文件路径,不会读取其他启动文件。
被ssh
调用时
在被ssh
调用时,或者标准输入是一个网络连接时,会触发一些特殊逻辑:在这种情况下,即使是non-login && non-interactive shell情况的组合,bash
仍然会读取/etc/bash.bashrc
和~/.bashrc
。
/* get the rshd/sshd case out of the way first. */
if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 &&
act_like_sh == 0 && command_execution_string)
{
run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) ||
(find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0);
/* If we were run by sshd or we think we were run by rshd, execute
~/.bashrc if we are a top-level shell. */
if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
{
maybe_execute_file (SYS_BASHRC, 1);
maybe_execute_file (bashrc_file, 1);
return;
}
}