1. 程式人生 > >Bash啟動時配置檔案的載入過程

Bash啟動時配置檔案的載入過程

當用戶登入系統時,會載入各種bash配置檔案,還會設定或清空一系列變數,有時還會執行一些自定義的命令。這些行為都算是啟動bash時的過程。

另外,有些時候登入系統是可以互動的(如正常登入系統),有些時候是無互動的(如執行一個指令碼),因此總的來說bash啟動型別可分為互動式shell和非互動式shell。更細分一層,互動式shell還分為互動式的登入shell和互動式非登入shell,非互動的shell在某些時候可以在bash命令後帶上"–login"或短選項"-l",這時也算是登入式,即非互動的登入式shell。

1.1 判斷是否互動式、是否登入式

判斷是否為互動式shell有兩種簡單的方法:

方法一:判斷變數"-",如果值中含有字母"i",表示互動式。

[[email protected]~]# echo $-
himBH
[[email protected]~]# vim a.sh
#!/bin/bash
echo $-
[[email protected]~]# bash a.sh
hB

方法二:判斷變數PS1,如果值非空,則為互動式,否則為非互動式,因為非互動式會清空該變數。

[[email protected]~]# echo $PS1
[\[email protected]\h \W]\$

判斷是否為登入式的方法也很簡單,只需執行"shopt login"即可。值為"on"表示為登入式,否則為非登入式。

[[email protected]~]# shopt login_shell  
login_shell     on
[[email protected]~]# bash

[[email protected]~]# shopt login_shell
login_shell off

所以,要判斷是互動式以及登入式的情況,可簡單使用如下命令:

echo $PS1;shopt login_shell

或者

echo $-;shopt login_shell

1.2 幾種常見的bash啟動方式

(1).正常登入(偽終端登入如ssh登入,或虛擬終端登入)時,為互動式登入shell。

[[email protected]~]# echo $PS1;shopt login_shell 
[\[email protected]\h \W]\$
login_shell     on

(2).su命令,不帶"–login"時為互動式、非登入式shell,帶有"–login"時,為互動式、登入式shell。

[[email protected]~]# su root
[[email protected]~]# echo $PS1;shopt login_shell 
[\[email protected]\h \W]\$
login_shell     off
[[email protected]~]# su -
Last login: Sat Aug 19 13:24:11 CST 2017 on pts/0
[[email protected]~]# echo $PS1;shopt login_shell
[\[email protected]\h \W]\$
login_shell     on

(3).執行不帶"–login"選項的bash命令時為互動式、非登入式shell。但指定"–login"時,為互動式、登入式shell。

[[email protected]~]# bash
[[email protected]~]# echo $PS1;shopt login_shell
[\[email protected]\h \W]\$
login_shell     off
[[email protected]~]# bash -l
[[email protected]~]# echo $PS1;shopt login_shell
[\[email protected]\h \W]\$
login_shell     on

(4).使用命令組合(使用括號包圍命令列表)以及命令替換進入子shell時,繼承父shell的互動和登入屬性。

[[email protected]~]# (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell)
1
[\[email protected]\h \W]\$
login_shell     on
[[email protected]~]# su
[[email protected]~]# (echo $BASH_SUBSHELL;echo $PS1;shopt login_shell)
1
[\[email protected]\h \W]\$
login_shell     off

(5).ssh執行遠端命令,但不登入時,為非互動、非登入式。

[[email protected]~]# ssh localhost 'echo $PS1;shopt login_shell'
login_shell     off

(6).執行shell指令碼時,為非互動、非登入式shell。但指定了"–login"時,將為非互動、登入式shell。

例如,指令碼內容如下:

[[email protected]~]# vim b.sh
#!/bin/bash
echo $PS1
shopt login_shell

不帶"–login"選項時,為非互動、非登入式shell。

[[email protected]~]# bash b.sh

login_shell off

帶"–login"選項時,為非互動、登入式shell。

[[email protected]~]# bash -l b.sh
login_shell     on

(7).在圖形介面下開啟終端時,為互動式、非登入式shell。
在這裡插入圖片描述

但可以設定為使用互動式、登入式shell。

在這裡插入圖片描述

1.3 載入bash環境配置檔案

無論是否互動、是否登入,bash總要配置其執行環境。bash環境配置主要通過載入bash環境配置檔案來完成。但是否互動、是否登入將會影響載入哪些配置檔案,除了互動、登入屬性,有些特殊的屬性也會影響讀取配置檔案的方法。

bash環境配置檔案主要有/etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc和/etc/profile.d/*.sh,為了測試各種情形讀取哪些配置檔案,先分別向這幾個配置檔案中寫入幾個echo語句,用以判斷該配置檔案是否在啟動bash時被讀取載入了。

echo "echo '/etc/profile goes'" >>/etc/_profile
echo "echo '~/.bash_profile goes'" >>~/.bash_profile
echo "echo '~/.bashrc goes'" >>~/.bashrc
echo "echo '/etc/bashrc goes'" >>/etc/bashrc
echo "echo '/etc/profile.d/test.sh goes'" >>/etc/profile.d/test.sh
chmod +x /etc/profile.d/test.sh

①.互動式登入shell或非互動式但帶有"–login"(或短選項"-l",例如在shell指令碼中指定"#!/bin/bash -l"時)的bash啟動時,將先讀取/etc/profile,再依次搜尋~/.bash_profile、~/.bash_login和~/.profile,並僅載入第一個搜尋到且可讀的檔案。當退出時,將執行~/.bash_logout中的命令。

但要注意,在/etc/profile中有一條載入/etc/profile.d/*.sh的語句,它會使用source載入/etc/profile.d/下所有可執行的sh字尾的指令碼。

[[email protected]~]# grep -A 8 \*\.sh /etc/profile  
for i in /etc/profile.d/*.sh ; do
    if [ -r "$i" ]; then
        if [ "${-#*i}" != "$-" ]; then
            . "$i"
        else
            . "$i" >/dev/null 2>&1
        fi
    fi
done

內層if語句中的【"${-#*i}" != “$-”】表示將"$-“從左向右模式匹配”*i"並將匹配到的內容刪除(即進行變數切分),如果"$-“切分後的值不等於”$-",則意味著是互動式shell,於是怎樣怎樣,否則怎樣怎樣。

同樣的,在~/.bash_profile中也一樣有載入~/.bashrc的命令。

[[email protected]~]# grep -A 1 \~/\.bashrc ~/.bash_profile
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

而~/.bashrc中又有載入/etc/bashrc的命令。

[[email protected]~]# grep -A 1 /etc/bashrc ~/.bashrc
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

其實/etc/bashrc中還有載入/etc/profile.d/*.sh的語句,但前提是非登入式shell時才會執行。以下是部分語句:

if ! shopt -q login_shell ; then   # We're not a login shell
...
    for i in /etc/profile.d/*.sh; do
        if [ -r "$i" ]; then
            if [ "$PS1" ]; then
                . "$i"
            else
                . "$i" >/dev/null 2>&1
            fi
        fi
    done
...
fi

從內層if語句和/etc/profile中對應的判斷語句的作用是一致的,只不過判斷方式不同,寫法不同。

因此,互動式的登入shell載入bash環境配置檔案的實際過程如下圖
在這裡插入圖片描述

以下結果驗證了結論:

Last login: Mon Aug 14 04:49:29 2017     # 新開終端登入時
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
 [[email protected]~]# ssh localhost        # ssh遠端登入時
[email protected]'s password:
Last login: Mon Aug 14 05:05:50 2017 from 172.16.10.1
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[[email protected]~]# bash -l        # 執行帶有"--login"選項的login時
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[[email protected]~]# su -          # su帶上"--login"時
/etc/profile.d/*.sh goes
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
[[email protected]~]# vim a.sh    # 執行shell指令碼時帶有"--login"時
#!/bin/bash -l
echo haha
[[email protected]~]# ./a.sh 
/etc/profile goes
/etc/bashrc goes
~/.bashrc goes
~/.bash_profile goes
haha

之所以執行shell指令碼時沒有顯示執行/etc/profile.d/*.sh,是因為它是非互動式的,根據/etc/profile中的【if [ “${-#i}" != “$-” ]】判斷,它將會把/etc/profile.d/.sh的執行結果重定向到/dev/null中。也就是說,即使是shell指令碼(帶”–login "選項),它也載入了所有bash環境配置檔案。

②.互動式非登入shell的bash啟動時,將讀取~/.bashrc,不會讀取/etc/profile和~/.bash_profile、~/.bash_login和~/.profile。

因此,互動式非登入shell載入bash環境配置檔案的實際過程為下圖內方框中所示(由/etc/bashrc載入/etc/profile.d/*.sh下的配置檔案):

在這裡插入圖片描述

例如,執行不帶"–login"的bash命令或su命令時。

[[email protected]~]# bash
/etc/profile.d/*.sh goes
/etc/bashrc goes
~/.bashrc goes
[[email protected]~]# su
/etc/profile.d/*.sh goes
/etc/bashrc goes
~/.bashrc goes

③.非互動式、非登入式shell啟動bash時,不會載入前面所說的任何bash環境配置檔案,但會搜尋變數BASH_ENV,如果搜尋到了,則載入其所指定的檔案。但有並非所有非互動式、非登入式shell啟動時都會如此,見情況④。

它就像是這樣的語句:

if [ -n "$BASH_ENV" ];`then
    . "$BASH_ENV"
fi

幾乎執行所有的shell指令碼都不會特意帶上"–login"選項,因此shell指令碼不會載入任何bash環境配置檔案,除非手動配置了變數BASH_ENV。

④.遠端shell方式啟動的bash,它雖然屬於非互動、非登入式,但會載入~/.bashrc,所以還會載入/etc/bashrc,由於是非登入式,所以最終還會載入/etc/profile.d/*.sh,只不過因為是非互動式而使得執行的結果全部重定向到了/dev/null中。

如果瞭解rsync,就知道它有一種遠端shell連線方式。所謂的遠端shell方式,是指通過網路的方式啟動bash並將bash的標準輸出關聯起來,就像它連線了一個遠端的shell守護程序一樣。一般由sshd實現這樣的連線方式,老版的rshd也一樣支援。

事實也確實如此,使用ssh連線但不登入遠端主機時(例如只為了執行遠端命令),就是遠端shell的方式,但它卻是非互動、非登入式的shell。

[[email protected]~]# ssh localhost echo haha
[email protected]'s password:
/etc/bashrc goes
~/.bashrc goes
haha

正如上文所說,它同樣載入了/etc/profile.d/*.sh,只不過/etc/bashrc中的if判斷語句【if [ “$PS1” ]; then】使得非互動式的shell要將執行結果重定向到/dev/null中