在 shell(Bash 是一種 shell) 中執行外部程序和腳本時,Linux 內核會啟動一個新的進程,以便在新的進程中執行指定的程序或腳本。內核知道該如何為編譯型的程序做這件事,但是對於腳本程序呢?當 shell 要求內核執行一個腳本文件時,內核是不知道該怎么辦的!所以它回應一個 "not executable format file" 的錯誤消息。Shell 收到這樣的消息后會做出類似下面的判斷:這不是個編譯型程序,那它肯定是一個 shell 腳本;接着就啟動一個新的 /bin/sh 副本來這些該程序。
當系統中只有一個 shell(/bin/sh) 時這並沒有什么問題。但是當前的系統中一般都存在多個 shell,比如 Bash、Dash等等。因此需要通過一種方式,告訴 Linux 內核應該以哪個 shell 來執行指定的腳本。實時上,這么做有助於執行機制的通用化,讓用戶可以直接引用任何的程序語言解釋器,而不僅僅是一個 shell。具體的方法是通過腳本文件中特殊的第一行來設置:在第一行的開頭處使用 #! 這兩個字符(英文一般稱為 shebang)。
當一個腳本中第一行是以 #! 這兩個字符開頭時,內核會掃描該行的其余部分,看是否可以找到可以用來執行該腳本文件的解釋器。所以這是一種非常通用的做法,因為除了 shell 我們還可以指定其它的解釋器,比如:
#!/usr/bin/awk # 這個腳本是一個 awk 程序
#!/bin/bash
直接指定 shell 的絕對路徑是一種經典的寫法。這樣內核會直接調用你指定的解釋器,並把腳本文件作為參數傳遞給它。這樣做的缺點也非常明顯,面對多如牛毛的 Linux 發行版,你無法保證所有系統中的 bash 程序都放置在 /bin 目錄下。當然其它程序的路徑就更無法保證了。
/usr/bin/env 命令
讓我們先來了解一下 /usr/bin/env 命令的執行方式,比如下面的命令:
$ env name=value name2=value2 program args
這會使用環境變量和由 name=value 和 name2=value2 指定的值擴展當前環境而形成的環境運行命令 program args。如果不包含任何參數,比如 name=value,那么將傳遞不經過修改的當前環境。因為 env 是外部命令,所以它並不知道 bash 中的別名,env 只是將程序和參數傳遞給 exec 調用。
#!/usr/bin/env bash
在了解了 /usr/bin/env 命令之后,讓我們來看看 shebang 的另一種寫法:
#!/usr/bin/env bash
你會看到越來越多的腳本采用了這種寫法。通過 /usr/bin/env 運行命令的好處是可以在當前環境中查找程序的默認版本。這樣,就不必在系統上的特定位置查找它,因為這些路徑在不同的系統中可能位於不同的位置。只要你指定的解釋器程序在你的 PATH 變量中,這種寫法就會找到它。當然,這么做的前提是 /usr/bin/env 必須存在。
這種寫法也是有缺點的,比如我們可以創建一個名稱為 bash 的程序,並把它的路徑添加到 PATH 變量的靠前位置,這樣就會使用你寫的假 bash 程序來執行腳本,而不是真正的 bash 程序,這是一個安全隱患。
個人的理解
#!/usr/bin/env bash 寫法
更靈活,可移植性較好,但是有安全風險。
#!/bin/bash 寫法
如果只考慮在單一的系統中執行,足夠了。