【Linux】載入順序
https://blog.miniasp.com/post/2021/07/26/Bash-and-Zsh-Initialization-Files
-
這張圖的第一層
Interactive?的意思Yes: 當你透過 Console 或 SSH 登入 Linux 主機,預設會進入互動模式 (Interactive shell),也就是這裡說的 Interactive 的意思。
No: 任何透過
bash -c '<command>'去執行的腳本,就屬於 非互動模式 (Non-Interactive shell) 的執行。 -
這張圖第二層的
Login shell?的意思基本上,透過 Console 或 SSH 登入 Linux 主機時,這個 Shell 就跑在所謂的 Login shell 模式下!
不過,當你透過 SSH 遠端執行一個命令,此時就不會啟動 Login shell 模式。而直接呼叫一個使用 Bash 執行的腳本,也不是 Login shell 模式。例如以下命令:
ssh user@host <COMMAND> -
這張圖第二層的
--login ?的意思就算你的 Bash 不是執行在 Login shell 模式,你一樣可以在呼叫
/bin/bash的時候特別加上--login參數,這樣也可以被視為是 Login shell 模式。例如以下這段範例,就會被視為使用 Login shell 模式啟動:
#!/bin/bash --login -
這張圖第三層的
$BASH_ENV是什麼環境變數?當你將 Bash 啟動在非互動模式,也沒有特別加上
--login參數的情況,同時這也是大多數執行的預設值,Bash 會優先尋找目前的環境變數中有沒有一個名為$BASH_ENV的變數,這個變數其實是指向一個檔案路徑。你從man bash可以發現,Bash 在非互動模式啟動的時候,預設會執行以下命令:if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi這也意味者,他會去 sourcing
"$BASH_ENV"這個檔案! -
這張圖第三層的
--noprofile是什麼參數?執行
/bin/bash的時候,可以額外加上--noprofile參數,加上之後就不會載入任何啟動檔。如果你沒有加上
--noprofile參數,也是一般大多數命令的執行方式,預設是會先載入系統全域的/etc/profile檔案。注意:系統全域的
/etc/profile檔案,還會額外載入/etc/profile.d/*.sh檔案。然後再從
$HOME目錄下依序找到這三個檔案執行,但重點是,他只會選擇一個檔案來執行,先找到的先執行,後面的就不會執行!~/.bash_profile~/.bash_login~/.profile
由於上述三個檔案,最終只有一個會被執行,所以這絕對是一個潛在的地雷!💥
我在多年前,就曾經因為我的
$HOME目錄下同時出現~/.bash_profile與~/.profile而發生系統異常,原來只要目錄中有出現~/.bash_profile檔案,就再也不會載入~/.profile檔案啊!!!在 Ubuntu 的作業系統中,預設是看不到
~/.bash_profile檔案的,建議都以~/.profile為主要的登入啟動檔!然而,大多數的
~/.profile登入啟動檔,都會在檔案中額外載入~/.bashrc檔案,因此有些 Bash 相關的環境設定,如shopt之類的,都會放在~/.bashrc檔案中。 -
這張圖第三層的
--rcfile <file>是什麼參數?當你不是以 Login shell 的方式啟動 Bash,啟動時也沒加上
--login參數,他就會去找你有沒有特別加上--rcfile <file>參數,明確載入你所指定的檔案路徑。如果你額外加上的是
--norc參數的話,那就代表你完全不想載入系統全域的/etc/bash.bashrc與$HOME目錄下的~/.bashrc檔案。如果你用
--rcfile <file>參數找不到指定的檔案,或完全沒用--rcfile <file>或--norc參數,也是大多數命令預設的方式,那麼 Bash 就會依序載入/etc/bash.bashrc與~/.bashrc檔案,兩個檔案都會載入。這種情境下,是不會載入~/.bash_profile,~/.bash_login或~/.profile檔案的!
可以完整釐清 Bash 所有啟動檔的載入條件與順序,心裡的感覺格外踏實,非常棒! 👍
完整的 Zsh 啟動檔載入順序
由於許多 macOS 用戶都採用 Zsh 為主,這邊我也特別研究了一下 Zsh 的啟動檔載入順序,基本上 Zsh 會依據以下順序載入:
-
~/.zshenv任何啟動情境下,都會載入這個檔案,請將各種環境變數請全部設定在這裡。
-
/etc/zsh/zprofile與~/.zprofile如果執行在 Login shell 才會依序執行
/etc/zsh/zprofile與~/.zprofile檔案。 -
/etc/zsh/zshrc與~/.zshrc如果執行在 Interactive 互動模式下,才會依序執行
/etc/zsh/zshrc與~/.zshrc檔案。 -
/etc/zsh/zlogin與~/.zlogin如果執行在 Login shell 下,最後才會依序執行
/etc/zsh/zlogin與~/.zlogin檔案。 -
~/.zlogout與/etc/zsh/zlogout當你用
exit或logout命令登出時,會自動依序執行~/.zlogout與/etc/zsh/zlogout檔案。
我只能說,Zsh 的啟動順序實在比 Bash 好理解太多了,也沒什麼地雷! 👍
總結幾種常見情境
-
直接呼叫某個 Shell Script
#!/bin/bash [ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive" shopt -q login_shell && echo "Login shell" || echo "Not login shell"Interactive?👉NoLogin shell?👉No--noprofile?👉No
執行時完全不會載入任何新的啟動檔設定!
-
使用 SSH 登入遠端主機
ssh user@host依序載入
/etc/profile-->/etc/bash.bashrc-->~/.profile[ ->~/.bashrc]Interactive?👉YesLogin shell?👉Yes--noprofile?👉No
注意: Bash 不會主動載入
~/.bashrc執行,而是我們通常在~/.profile會載入~/.bashrc執行。 -
使用 SSH 登入遠端主機後,執行
bash命令ssh user@host剛登入,依序載入
/etc/profile-->/etc/bash.bashrc-->~/.profile[ ->~/.bashrc]Interactive?👉YesLogin shell?👉Yes--noprofile?👉No
然後我們在遠端主機執行
bash命令,進入下一層 Shell 環境:bash登入後執行
bash預設是互動模式,會依序載入/etc/bash.bashrc-->~/.bashrcInteractive?👉YesLogin shell?👉No--rcfile <file> ?👉No--norc ?👉No
-
使用 SSH 登入遠端主機後,執行
bash -c '<command>'命令ssh user@host剛登入,依序載入
/etc/profile-->/etc/bash.bashrc-->~/.profile[ ->~/.bashrc]Interactive?👉YesLogin shell?👉Yes--noprofile?👉No
bash -c '[ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"'登入後執行
bash -c命令屬於非互動模式,而且沒有$BASH_ENV的情況下,不會載入任何啟動檔!Interactive?👉NoLogin shell?👉No$BASH_ENV👉No
-
使用 SSH 執行遠端命令
ssh user@host '[ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"' ssh user@host 'shopt -q login_shell && echo "Login shell" || echo "Not login shell"'透過 SSH 執行遠端命令,預設將會跑在 Non-Interactive 模式下,但還是會依序載入
/etc/bash.bashrc-->~/.bashrcInteractive?👉NoLogin shell?👉No
如果你想在執行遠端命令時載入額外的啟動檔,也可以這樣寫:
ssh user@host "source /etc/profile; source ~/.profile; /your/script.sh" -
使用 SSH 執行遠端命令,透過
bash呼叫另一個命令ssh user@host -t 'bash -c '"'"'[ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"'"'"'' ssh user@host -t 'bash -c '"'"'shopt -q login_shell && echo "Login shell" || echo "Not login shell"'"'"''要在單引號(
')的字串中間加入一個單引號,必須輸入五個字元'"'"'才能代表一個單引號!(噁心的語法)在 ssh 執行時加入
-t參數,可以在遠端執行時取得一個 pseudo-terminal allocation (pts/0)!透過 SSH 執行遠端命令,預設將會跑在 Non-Interactive 模式下,但還是會依序載入
/etc/bash.bashrc-->~/.bashrcInteractive?👉NoLogin shell?👉No
第二層
bash應該是跑在 Non-Interactive 模式,但是卻依序載入/etc/bash.bashrc-->~/.bashrc檔案,這部分我還沒辦法理解為什麼會這樣,感覺透過 SSH 執行遠端程式,預設就會載入這兩個檔案!Interactive?👉NoLogin shell?👉No
底下這段是讓命令跑在 Interactive 模式,但載入啟動檔的順序竟然跟 Non-Interactive 竟然一樣!
ssh user@host -t 'bash -i -c '"'"'[ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"'"'"'' ssh user@host -t 'bash -i -c '"'"'shopt -q login_shell && echo "Login shell" || echo "Not login shell"'"'"''簡單來說,使用 SSH 直接遠端命令時,使用
-i(互動模式) 或不使用-i對載入啟動檔沒有什麼兩樣,不過都會載入兩次。不過還是有一個地方不同,如果你的啟動檔有設定 alias 的話,只有使用-i互動模式才能使用。例如以下這段命令,就會得到
bash: ll: command not found的錯誤:ssh user@host -t 'll'ssh user@host -t bash -c 'll'如果改用
-i來執行,就可以正常執行ll這個 alias 命令:ssh user@host -t bash -i -c 'll' -
使用 SSH 執行遠端命令,並以 Login shell 啟動 Bash
ssh user@host bash -l -c 'set'第一層
bash依序載入/etc/bash.bashrc-->~/.bashrc第二層
bash依序載入/etc/profile-->~/.profile[ ->~/.bashrc]
