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
2. Non-Login Shell 初始化时配置文件读取顺序的伪代码示意:
execute /etc/bash.bashrc
IF ~/.bashrc exists THEN
execute ~/.bashrc
END IF
// from session.c of OpenSSH 5.9p1
/*
* Execute the command using the users shell. This uses the -c
* option to execute the command.
*/
argv[0] = (char *) shell0;
argv[1] = "-c";
argv[2] = (char *) command;
argv[3] = NULL;
execve(shell, argv, env);
perror(shell);
exit(1);
而Bash中的相关代码:
// from config-top.h
/* 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). */
/* #define SSH_SOURCE_BASHRC */
// from shell.c of Bash 4.2
#ifdef SSH_SOURCE_BASHRC
run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) ||
(find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0);
#else
/* ... */
/* 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)
{
#ifdef SYS_BASHRC
# if defined (__OPENNT)
maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1);
# else
maybe_execute_file (SYS_BASHRC, 1);
# endif
#endif
maybe_execute_file (bashrc_file, 1);
return;
}
从rsync(1)的DIAGNOSTICS部分可以看到,rsync非常依赖于shell执行时没有任何输出。如果在~/.bashrc中source了~/.bash_profile,而~/.bash_profile中又有无关的文字输出,就会导致从其他服务器rsync到此服务器失败,报错信息为"protocol version mismatch — is your shell clean?"。这也回答了上一节的问题:*banner类信息不能放在~/.bashrc文件内*
几个bash配置文件的说明:
另外,/etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系.
其实这个问题的核心就是 Shell 初始化时读取配置文件的步骤,而 Shell 又可以分为两类:Login Shell 和 Non-login Shell。参考博客 Execution sequence for .bash_profile,...:
1. Login Shell 初始化时配置文件读取顺序的伪代码示意:
2. Non-Login Shell 初始化时配置文件读取顺序的伪代码示意:
最后,Mac 的终端默认开启为 Login Shell。而 Ubuntu 的 Gnome Terminal 默认开启的是 Non-Login Shell.
原文见 https://gist.github.com/1564928
Shell的不同分类
根据启动Bash Shell的方式不同,对Shell有两种分类方式
=
登录Shell与非登录Shell
=
根据Shell的启动方式不同,可以将Shell分为
1. Login Shell
2. Non-login Shell
Login Shell的定义是,当前shell的
argv[0]
的第一个字符是-
,或当前shell使用了-l
(--login
) 选项。只要满足以上的两个条件的任意一个,bash就会表现得Login Shell一样。例如,以下列出的场景下,bash都是login shell:
1. 执行
bash -l -c 'w'
( 使用了-l选项 )2. 执行
su -l admin
( 运行的Shell的argv[0]
第一个字符是-
)3. 执行
login -f
( 运行的Shell的argv[0]
第一个字符是-
)4. 将当前目录加入到
$PATH
,根据以下命令创建到bash的链接并执行 ( 运行的Shell的argv[0]
第一个字符是-
)=
交互与非交互Shell
=
同时,根据Shell启动参数的不同,还可以将Shell分为
1. Interactive Shell
2. Non-interactive Shell
Interactive Shell的定义很明确:
$-
环境变量中包含字符i
的Shell就是Interactive Shell以下场景中,bash属于Interactive Shell
1. bash执行时没有加上非选项参数 [^noa]
2. bash执行时没有加上
-c
选项3. bash执行时,加上了
-i
选项以下场景中,bash属于Non-interactive Shell
1. 使用
rsync -e ssh
同步文件(-c
选项)2. 其他基于ssh的文件传输,如git、svn等(基本都启用了
-c
选项)不同Shell中启动时执行的文件
Bash启动时会按照一定的顺序载入rc文件,定义
PS1
、JAVA_HOME
等环境变量,执行特定的脚本等。按照两种Shell分类排列组合,一共有4种组合。各个组合下启动载入rc文件的顺序和数量有区别,以下分别列出:
=
登录/非交互Shell & 登录/交互Shell
=
bash会依次执行以下文件
1.
/etc/profile
[^sysconfdir]
2.
~/.bash_profile
3.
~/.bash_login
4.
~/.profile
=
非登录/非交互Shell
=
执行
$BASH_ENV
环境变量中指定的脚本=
非登录/交互Shell
=
1. “全局”bashrc(编译时定义
SYS_BASHRC
,默认为/etc/bash.bashrc
)2.
~/.bashrc
[^exception_bashrc]SSH远程登录服务器执行命令场景分析
上面在分析交互/非交互、登录/非登录Shell的时候,特地省略了ssh到远程服务器执行命令这种场景下Shell类型的分类,放在这里分析。
相关的资料不是很多,不管是
bash(1)
还是ssh(1)
还是sshd(1)
,都没有对这个场景详细说明过。经过多次尝试,在不同的rc文件里echo特定字符串,得出结论。如果分析有问题,请大家纠正。使用的命令为
ssh 127.0.0.1 w
进行分析,会发现这个命令执行时会载入~/.bashrc
,不会载入/etc/profile
等文件在
~/.bashrc
文件中放置命令ps -p $$ -o args=
,可获得载入~/.bashrc
文件的进程命令行为bash -c w
,是一个非登录、非交互Shell。根据bash(1)
中INVOCATION
段的说明,此时应该只载入$BASH_ENV
环境变量的脚本,不会载入/etc/bash.bashrc
或~/.bashrc
脚本进一步研究OpenSSH源码和Bash源码。OpenSSH中执行shell部分的代码如下:
而Bash中的相关代码:
可以看到,只要编译前在
config-top.h
中定义SSH_SOURCE_BASHRC
,那么尽管Bash在被sshd fork出来的时候加上了-c
选项,也确实是non-login non-interactive shell,只要发现SSH_CLIENT
或者SSH2_CLIENT
环境变量存在,就仍然会依次载入SYS_BASHRC
、~/.bashrc
文件。这个结论非常重要,因为包括svn、git、rsync在内很多命令都使用ssh作为传输层。如果
/etc/bash.bashrc
、~/.bashrc
文件配置不合理,这些命令的执行都会有问题。请注意,在此问题上,各发行版自带bash的行为可能不同。Debian 5和6的补丁都设置了
SSH_SOURCE_BASHRC
,用户自己编译时可能未设定,因此也不能简单地认为通过ssh执行命令时服务器上的bash一定载入bashrc系列文件,更不可依赖bashrc来执行初始化命令。常见问题分析
=
Banner、Motd类提示信息应该放在哪里?
=
某些服务器可以在用户登录时加入一些提示信息,提示用户操作等。这样的信息仅需要在用户登录时显示,因此可以将此类信息放在login shell才会载入的文件中,如
/etc/profile
、~/.bash_profile
、~/.bash_login
、~/.profile
是否能将此类提示信息放在
~/.bashrc
文件内呢?下面说明=
在
~/.bashrc
文件是否能source ~/.bash_profile
呢?=
以前遇到过一个问题。跳板机上在
~/.bashrc
里显示了一些banner信息,导致诡异地无法从其他服务器rsync
文件到这台跳板机上。从
rsync(1)
的DIAGNOSTICS
部分可以看到,rsync
非常依赖于shell执行时没有任何输出。如果在~/.bashrc
中source了~/.bash_profile
,而~/.bash_profile
中又有无关的文字输出,就会导致从其他服务器rsync到此服务器失败,报错信息为"protocol version mismatch — is your shell clean?"。这也回答了上一节的问题:*banner类信息不能放在~/.bashrc
文件内*但是反过来,在
~/.bash_profile
中source ~/.bashrc
是可以的,但是使用时要非常小心(容易引起循环引用,导致问题)=
ssh到服务器执行
java -version
为什么版本和实际应用使用的不一致?=
目前
JAVA_HOME
、PATH
等环境变量的定义是在/etc/profile
。ASA比较常见的操作是脚本跑一个集群,ssh到相应服务器上确认java版本是否正确。这个时候shell只会载入~/.bashrc
,不会载入/etc/profile
。解决方法可以用以下任一:
source /etc/profile
再执行java -version
来判断ssh remote-host 'bash -l -c "java -version"'
[^noa]: Non-option Arguments,即不以
-
开头的参数。例如,bash -l test.sh
,-l
是option argument,test.sh
是non-option argument[^sysconfdir]
: 严格地说,是${SYSCONFDIR}/profile
文件。${SYSCONFDIR}
是程序编译时,传递给configure
脚本的--sysconfdir
选项指定的目录。编译时没有指定--sysconfdir
则使用--prefix
指定的路径下etc
文件夹。默认${SYSCONFDIR}
为/
[^exception_bashrc]: 存在例外情况。如果编译bash时加上了
#define SYS_BASHRC /etc/bashrc
或CPPFLAGS
加上了-DSYS_BASHRC=/etc/bashrc
,那么任何时候~/.bashrc
被载入前,${SYS_BASHRC}
文件先被载入~/.bash_profile
只在当前用户登入的时候加载,~/.bashrc
在每次 Bash 初始化的时候都会加载。经常看到 .bash_profile 里的内容是
有时候我也搞不清其中的关系,不过我一般都是把启动配置放在.bashrc下面,如果不管用的话,再创建上述.bash_profile。