5

關於 Linux 下 Bash 與 Zsh 啟動檔的載入順序研究

 2 years ago
source link: https://blog.miniasp.com/post/2021/07/26/Bash-and-Zsh-Initialization-Files
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Linux 用了二十多年,從沒認真想過 Login Shell 載入的啟動檔順序為何,我們經常會看到 .bashrc, .bash_profile, .bash_login, .profile 諸如此類的檔案,在安裝一些工具或系統的時候,也多多少少會需要設定這些檔案,加入一些環境變數或其他設定之類的。除了我常用的 Bash 以外,外面還有 Zsh 也很多人用,這時到底要編輯哪個檔案才是正確的呢?我將用這篇文章來好好釐清這個真相。

完整的 Bash 啟動檔載入順序

由於我大多使用 Bash 為我主要的 Shell 環境,這裡我要大推 Bash Initialisation Files 這篇文章中的一張流程圖,他大至交代了 Bash 載入不同檔案的各種情境,我也逐一測試過不同的情境,確實是 100% 正確無誤!

完整的 Bash 啟動檔載入順序

這張圖並沒辦法完整講述 Bash 啟動檔的載入順序,但是也涵蓋了十之八九!

  • 這張圖的第一層 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 目錄下依序找到這三個檔案執行,但重點是,他只會選擇一個檔案來執行,先找到的先執行,後面的就不會執行!

    1. ~/.bash_profile
    2. ~/.bash_login
    3. ~/.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 會依據以下順序載入:

  1. ~/.zshenv

    任何啟動情境下,都會載入這個檔案,請將各種環境變數請全部設定在這裡。

  2. /etc/zsh/zprofile~/.zprofile

    如果執行在 Login shell 才會依序執行 /etc/zsh/zprofile~/.zprofile 檔案。

  3. /etc/zsh/zshrc~/.zshrc

    如果執行在 Interactive 互動模式下,才會依序執行 /etc/zsh/zshrc~/.zshrc 檔案。

  4. /etc/zsh/zlogin~/.zlogin

    如果執行在 Login shell 下,最後才會依序執行 /etc/zsh/zlogin~/.zlogin 檔案。

  5. ~/.zlogout/etc/zsh/zlogout

    當你用 exitlogout 命令登出時,會自動依序執行 ~/.zlogout/etc/zsh/zlogout 檔案。

我只能說,Zsh 的啟動順序實在比 Bash 好理解太多了,也沒什麼地雷! 👍

總結幾種常見情境

  1. 直接呼叫某個 Shell Script

    #!/bin/bash
    [ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"
    shopt -q login_shell && echo "Login shell" || echo "Not login shell"
    
    • Interactive? 👉 No
    • Login shell? 👉 No
    • --noprofile? 👉 No

    執行時完全不會載入任何新的啟動檔設定!

  2. 使用 SSH 登入遠端主機

    ssh user@host
    

    依序載入 /etc/profile --> /etc/bash.bashrc --> ~/.profile [ -> ~/.bashrc ]

    • Interactive? 👉 Yes
    • Login shell? 👉 Yes
    • --noprofile? 👉 No

    注意: Bash 不會主動載入 ~/.bashrc 執行,而是我們通常在 ~/.profile 會載入 ~/.bashrc 執行。

  3. 使用 SSH 登入遠端主機後,執行 bash 命令

    ssh user@host
    

    剛登入,依序載入 /etc/profile --> /etc/bash.bashrc --> ~/.profile [ -> ~/.bashrc ]

    • Interactive? 👉 Yes
    • Login shell? 👉 Yes
    • --noprofile? 👉 No

    然後我們在遠端主機執行 bash 命令,進入下一層 Shell 環境:

    bash
    

    登入後執行 bash 預設是互動模式,會依序載入 /etc/bash.bashrc --> ~/.bashrc

    • Interactive? 👉 Yes
    • Login shell? 👉 No
    • --rcfile <file> ? 👉 No
    • --norc ? 👉 No
  4. 使用 SSH 登入遠端主機後,執行 bash -c '<command>' 命令

    ssh user@host
    

    剛登入,依序載入 /etc/profile --> /etc/bash.bashrc --> ~/.profile [ -> ~/.bashrc ]

    • Interactive? 👉 Yes
    • Login shell? 👉 Yes
    • --noprofile? 👉 No
    bash -c '[ -z "$PS1" ] && echo "Non-Interactive" || echo "Interactive"'
    

    登入後執行 bash -c 命令屬於非互動模式,而且沒有 $BASH_ENV 的情況下,不會載入任何啟動檔!

    • Interactive? 👉 No
    • Login shell? 👉 No
    • $BASH_ENV 👉 No
  5. 使用 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 --> ~/.bashrc

    • Interactive? 👉 No
    • Login shell? 👉 No

    如果你想在執行遠端命令時載入額外的啟動檔,也可以這樣寫:

    ssh user@host "source /etc/profile; source ~/.profile; /your/script.sh"
    
  6. 使用 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 --> ~/.bashrc

    • Interactive? 👉 No
    • Login shell? 👉 No

    第二層 bash 應該是跑在 Non-Interactive 模式,但是卻依序載入 /etc/bash.bashrc --> ~/.bashrc 檔案,這部分我還沒辦法理解為什麼會這樣,感覺透過 SSH 執行遠端程式,預設就會載入這兩個檔案!

    • Interactive? 👉 No
    • Login 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'
    
  7. 使用 SSH 執行遠端命令,並以 Login shell 啟動 Bash

    ssh user@host bash -l -c 'set'
    

    第一層 bash 依序載入 /etc/bash.bashrc --> ~/.bashrc

    第二層 bash 依序載入 /etc/profile --> ~/.profile [ -> ~/.bashrc ]


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK