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。