Linux 上的 Shebang 是在那裡定義的?
在 Linux 的指令執行檔的開頭,常常會看到以 #!
字串開頭的宣告,像是:
#!/bin/bash
#!/usr/bin/env python3
#!/bin/sh
這個開頭宣告的作用,是告訴系統要用什麼指令來執行該指令執行檔。
例如: #!/usr/bin/env python3
開頭的,就會以 /usr/bin/env python3
這個指令來執行。
不過,這開頭的 #!
這一行是那裡讀取並判讀的呢? 是 Bash? glibc? … or kernel?
答案是,直接由 Linux kernel 處理的,其對應的 source code 是在
fs/binfmt_script.c
裡的 load_script()
函式:
static int load_script(struct linux_binprm *bprm)
{
const char *i_name, *i_sep, *i_arg, *i_end, *buf_end;
struct file *file;
int retval;
/* Not ours to exec if we don't start with "#!". */
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
return -ENOEXEC;
(出處: https://github.com/torvalds/linux/blob/v5.18/fs/binfmt_script.c#L41)
同時,Linux kernel config 有個設定選項 BINFMT_SCRIPT
,來決定是否支援 #!
這樣的用法。
(出處: https://github.com/torvalds/linux/blob/v5.18/fs/Kconfig.binfmt#L99)
可以使用下列指令來檢查系統的支援狀況:
zless /proc/config | grep -e "^CONFIG_BINFMT_SCRIPT="
預期會得到:
CONFIG_BINFMT_SCRIPT=y
一般來說,絕大多數的 Linux 系統都內建支援 #!
的語法。
但還是遇有一些 embedded system 或是資安考量的系統,會拿掉這個設定。
若在設定 at
, cron
的排程設定時,發現程式未如預期執行時,可順便檢查這一個設定。
Q: 關於 Shebang 的歷史?
可參考維基百科的說明:
https://en.wikipedia.org/wiki/Shebang_(Unix)
Q: Linux 上大約何時支援?
從官方的 git 紀錄,支援 #!
宣告的功能,至少在 v2.6.12 時就己經存在:
https://github.com/torvalds/linux/blob/v2.6.12/fs/binfmt_script.c#L26
實際時間應該更早。 不過現下已經很少遇到這類舊系統,所以暫時沒再往前追溯。
Q: 為什麼有的會用 #!/usr/bin/env
開頭?
以 Bash 為例,有些系統會放在 /bin/bash
的位置,有些會放在 /usr/bin/bash
。
若我的 script 寫 #!/bin/bash
,那遇到放在 /usr/bin/bash
位置的系統,就會路徑不符而失效。
所以,就有了這一支 env
的程式,他在所有系統都固定放在 /usr/bin/env
這個位置。
當用下述的指令時:
/usr/bin/env bash
env
就會根據該系統的環境設定(ex: $PATH
, /etc/environment
, …),去找出對應的 bash
的位置去執行。
如果一來,用上述的宣告在橫跨不同系統時,都能執行,而不需要再調整開頭的 Shebang 路徑。
進一步的資訊,可參考 Shebang (Unix) - Wikipedia 的 “Portability » Program location” 一節。
2024-04-07 創意用法
偶然看到 用 shebang 掛起 Docker instance – Gea-Suan Lin’s BLOG 一文,提到 Shebang 的某種創意用法:
adtac/595b5823ef73b329167b815757bbce9f 利用 Shebang 的用法,把 Dockerfile
的檔案開頭設爲
#!/usr/bin/env -S bash -c "docker run -p 8080:8080 -it --rm \$(docker build --progress plain -f \$0 . 2>&1 | tee /dev/stderr | grep -oP 'sha256:[0-9a-f]*')"
同時,將 Dockerfile
改爲可執行檔 (ex: chmod +x Dockerfile
) 。之後即可下 ./Dockerfile
指令去執行它。