# Linux 腳本首行的秘密:究竟 `#!/usr/bin/env bash` 與 `#!/bin/bash` 代表什麼? 在 [[Linux]] 中編寫腳本時,我們習慣性地會在最上方寫上這行程式碼。 ```bash #!/usr/bin/env bash ``` 或者 ```bash #!/bin/bash ``` 表面上看起來像註解,但這一行的**真實身份**究竟是什麼?它們之間又存在什麼差異呢?本文將深入解析這行程式碼的奧秘,並探討在不同情境下應選擇哪種寫法。 --- ## 1. 這不是註解:什麼是 shebang?{#sec-9f8041c620ba} 以 `#!` 開頭的這一行,我們稱之為 **shebang**。 ```bash #!/bin/bash ``` 對於 Shell 來說,因為它以 `#` 開頭,所以看起來確實像是「註解」。 然而,對於**作業系統(核心)**而言,這並非註解,而是: > 「指示作業系統,告知執行此腳本的**直譯器程式**位於何處的指令。」 換句話說, * `#!/bin/bash` → 「請使用 `/bin/bash` 來執行此檔案。」 * `#!/usr/bin/env bash` → 「請透過 `env` 尋找 `bash` 直譯器,並使用它來執行此檔案。」 這就是它們各自的含義。 --- ## 2. 核心如何執行腳本?{#sec-33d96b8d8f01} 將執行過程極度簡化,大致如下: 1. 使用者執行具有執行權限的腳本 ```bash chmod +x script.sh ./script.sh ``` 2. 核心讀取 `script.sh` 3. 檢查檔案的**前兩個字元**是否為 `#!` 4. 若是,則將該行剩餘部分 * 識別為「直譯器的路徑 + 參數」,並 * 執行該程式,同時**將腳本檔案路徑作為參數傳遞** 例如,如果 `script.sh` 的首行是這樣: ```bash #!/bin/bash ``` 核心實際執行的動作,大致可以這樣理解: ```bash /bin/bash script.sh ``` 換句話說,核心替我們執行了類似於我們手動輸入 `bash script.sh` 的操作。 > 注意: > `#!/` 前面**不能有任何空格**。 > 檔案的**第一個字元**必須是 `#`,第二個是 `!`。 --- ## 3. `#!/bin/bash` 的含義與特性 {#sec-52f9e12dce0d} 這是最常見的形式。 ```bash #!/bin/bash ``` ### 含義 {#sec-a97a945792d4} * 「這個腳本是 **bash 腳本**,且 `bash` 位於 `/bin/bash`。」 * 核心在執行此腳本時,總是會執行 `/bin/bash`。 ### 優點 {#sec-a01bf708579d} * **明確性**:由於總是使用 `/bin/bash`,因此很容易預測將使用哪個 bash 版本。 * **效能/簡潔性**:直接執行,不需透過 `env`,因此沒有路徑搜尋的過程。 * 在許多 [[Linux]] 發行版中,`/bin/bash` 實際上被視為「標準位置」。 ### 缺點 {#sec-89064c01f1ea} * **可能不具可攜性** * 在某些系統上,bash 可能位於不同的路徑,例如 `/usr/bin/bash`、`/usr/local/bin/bash` 等。 * 有些系統可能根本沒有安裝 bash,只有 `/bin/sh`。 * 特別是在 **macOS, BSD 系列, NixOS, 某些容器環境**等系統中,路徑可能有所不同。 --- ## 4. `#!/usr/bin/env bash` 的含義與特性 {#sec-97cd6f02458d} 這是目前腳本中常見的形式。 ```bash #!/usr/bin/env bash ``` 這裡的關鍵是 `/usr/bin/env`。 * `env` 是一個協助「設定/檢查環境變數 + 在 PATH 中搜尋程式」的工具程式。 * 核心實際執行的動作,可以這樣理解: ```bash /usr/bin/env bash script.sh ``` * `env` 會查看系統的 `PATH` 環境變數,並在其中尋找並執行 `bash` 可執行檔。 ### 優點 {#sec-9bc2075d78b3} 1. **可攜性高(在多種環境中運作良好)** * 無論 bash 位於 `/bin/bash`、`/usr/bin/bash` 還是 `/usr/local/bin/bash`, * 只要正確地註冊在 PATH 中,`env` 就能找到它。 2. **使用符合使用者環境的 bash** * 如果使用者自定義了 `PATH`,並設定優先使用特定版本的 bash,那麼腳本就會使用該版本。 3. **在 Python 等語言中也採用相同模式** ```sh #!/usr/bin/env python3 ``` ### 缺點 {#sec-0ba7b1b3de08} 1. **前提是 `/usr/bin/env` 必須存在** * 儘管它幾乎存在於所有現代 Unix/[[Linux]] 系統中,但在極特殊環境下可能不存在。 2. **可能因 PATH 設定而選用不同的直譯器** * 如果 PATH 設定混亂,或者在搜尋路徑中意外地找到了非預期的 bash 版本,可能會執行到錯誤的版本。 3. **從安全角度來看,有時需要特別注意** * 在對安全性極為敏感的環境中,人們可能會偏好使用**絕對路徑**,而不喜歡基於 PATH 來尋找直譯器的方式。 --- ## 5. 兩種方式比較總結 {#sec-f327651f1197} 以下透過簡要比較表進行整理。 | 類別 | #!/bin/bash | #!/usr/bin/env bash | | ---------------- | -------------- | ----------------------- | | 直譯器位置指定方式 | 固定絕對路徑 | 透過 `PATH` 搜尋 | | 可攜性(系統相容性) | 較低(路徑不同則失效) | 較高(只要 bash 在 PATH 中即可) | | 使用哪個 bash | 總是 `/bin/bash` | 在 PATH 中找到的第一個 `bash` | | 預期版本保證 | 相對容易 | 可能因 PATH 狀態而異 | | 安全性/控制力 | 更強(路徑固定) | 略為寬鬆(依賴 PATH) | | 一般最新趨勢 | 相對舊式風格 | 近來較推薦此方式 | --- ## 6. 何時該選擇哪種方式?{#sec-a9bac1ab9ba4} 對於「究竟我該用哪種?」這個問題,我們將根據不同情境進行整理。 ### 1) 個人/團隊開發用腳本(一般開發環境) {#sec-fc5216000f29} * 通常建議使用以下方式: ```bash #!/usr/bin/env bash ``` * 原因: * 在開發者直接管理的伺服器、本地環境、CI 環境等中,bash 的位置可能不同。 * 基於 PATH 搜尋並執行更具彈性,且現代的工具/腳本也偏好此方式。 ### 2) 適用於特定伺服器環境的營運腳本 {#sec-204ac6ba64d3} * 例如,若公司內所有伺服器都統一將 bash 安裝在 `/bin/bash`, * 且伺服器環境相當固定,則: ```bash #!/bin/bash ``` * 原因: * 確保始終使用相同的直譯器。 * 可以減少因 PATH 修改而導致的意外行為。 ### 3) 如果真的想讓腳本具備極高的可攜性? {#sec-6ef1b12d728e} * 如果環境中「可能根本沒有 bash」,那麼 首先應該考慮該腳本是否真的適合依賴 bash。 * 如果可能,請使用 `sh` 編寫: ```bash #!/bin/sh ``` * 這樣可以在更廣泛的環境中執行。 但請注意,不能使用 bash 特有的語法(如 `[[ ]]`、陣列、擴展字串處理等)。 --- ## 7. 使用 `#!/usr/bin/env bash` 的實用技巧 {#sec-e5a739214684} ### 1) 腳本執行方法 {#sec-83e5cc5b64a5} 要正確利用 shebang,不應該只這樣做: ```bash bash script.sh # ← 這樣執行 shebang 幾乎沒有意義 ``` 建議以下列方式使用: ```bash chmod +x script.sh # 賦予執行權限 ./script.sh # 直接執行 ``` 這樣核心才會讀取 `#!`,並使用指定的直譯器。 ### 2) 確認參數傳遞 {#sec-90ca3c9235bf} 例如,假設我們建立了一個 `test.sh` 腳本如下: ```bash #!/usr/bin/env bash echo "直譯器: $0" echo "參數: $@" ``` 執行: ```bash chmod +x test.sh ./test.sh hello world ``` 輸出: ```text 直譯器: ./test.sh 參數: hello world ``` 這裡的 `$0` 是「腳本檔案本身的的路徑」, 只需記住核心實際上是像 `/usr/bin/env bash test.sh hello world` 這樣執行即可。 --- ## 8. 常見問題 {#sec-99b0325925f3} ### 我曾看過沒有 shebang 的腳本,這是怎麼回事? {#sec-751234a46d29} * 沒錯,**shebang 並非總是必需的。** * 如果像下面這樣直接指定直譯器來執行,則不需要 shebang。即使寫了 shebang 也會被忽略。 ```bash bash myscript.sh python3 myscript.py ``` * 但是,如果想以下列方式執行,則 shebang 是必需的: ```bash ./myscript.sh ./myscript.py ``` * 特別是對於提供給他人使用的「工具型」腳本,**shebang 幾乎可以說是必不可少**。 ### 「除了 `#!/usr/bin/env`,我還看過 `#!/bin/env` 也能用?」 {#sec-6de89a827da4} * 在某些系統上,確實可能存在 `/bin/env`。 * 然而,通常 `/usr/bin/env` 是更為通用的標準位置。 * 如果沒有特殊理由,使用 `#!/usr/bin/env …` 會更安全。 --- ## 9. 總結 {#sec-971578a5a9d1} * `#!/usr/bin/env bash` 或 `#!/bin/bash` **並非註解,而是告知核心「應使用哪個直譯器來執行此腳本」的指令**。 * `#!/bin/bash` * 總是使用 `/bin/bash` → **在固定環境中可預測,但可能不具可攜性** * `#!/usr/bin/env bash` * 在 `PATH` 中尋找 bash 並使用 → **更具彈性與可攜性,但會受 PATH 狀態影響** * 在一般的開發/部署環境中,如果以「現代風格」編寫腳本, 則**建議將 `#!/usr/bin/env bash` 作為預設選項**。 ![Linux 腳本中的 shebang 示意圖](https://blog.mikihands.com/media/editor_temp/6/692ed85d-0eb6-4fd1-ab7a-22a2d189175d.png "shebang 的運作原理圖")