有些bash指令碼寫的不規範,沒有在檔案開頭寫#!,但是卻能直接執行,可是如果看核心代碼,shell指令碼的載入函數中的開頭就會判斷,如果沒有#!的話就會返回錯誤:
static int load_script(struct linux_binprm *bprm,struct pt_regs *regs)
{
...
if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!') || (bprm->sh_bang))
return -ENOEXEC;
...
}
為何在linux上寫bash指令碼時檔案開頭不寫#!也能成功調用?bash中執行一條鍵盤命令時調用的是shell_execve函數,該函數封裝了很多邏輯:
int shell_execve (command, args, env)
{
execve (command, args, env);
if (errno != ENOEXEC) {
//非檔案格式的錯誤,返回出錯
} else {
int larray = array_len (args) + 1;
int i, should_exec = 0;
int fd = open (command, O_RDONLY);
if (fd != -1) {
unsigned char sample[80]; //讀取檔案頭的80個位元組,然後進行格式判斷
int sample_len = read (fd, &sample[0], 80);
...//如果是空檔案,則正確返回,bash容忍這種情況
if (sample_len > 0 && sample[0] == '#' && sample[1] == '!')
return (execute_shell_script(...));//執行shell指令碼,其實現過程幾乎和核心的script_format結構對象中的
load_binary回呼函數一模一樣,主要用於不識別#!的作業系統
else if (check_binary_file (sample, sample_len))
//如果是二進位檔案,則bash是不會逐行協助執行的,出錯返回
}
...//bash會幫你執行command:
/*
args[0] = shell_name; //就是本shell的全路徑
args[1] = command; //要執行的指令碼
execve (shell_name, args, env); //重新執行
*/
}
以下是上述shell_execve函數依賴的協助函數和一些宏定義:
由於bash是通過字元的可見度來判斷是否是二進位檔案的,由於空格/定位字元等也是不可見的字元,然而它們確實是屬於文本ascii碼的,因此需要單獨進行過濾:
#define isspace(c) ((c) == ' ' || (c) == '/t' || (c) == '/n' || (c) == '/f')
文本字元包括字母字元,數字字元以及除此之外的可以列印字元:
#define isprint(c) (isletter(c) || digit(c) || ispunct(c))
判斷讀取的80個位元組是否全部是文本字元,只要有一個不是就將該檔案判定為二進位檔案,雖然前80位元組的字元是文本不能保證後面的就一定是文本,可是bash畢竟需要做出一個權衡和一個假設:
int check_binary_file (sample, sample_len)
{
for (i = 0; i < sample_len; i++) {
if (sample[i] == '/n')
break;
if (!isspace (sample[i]) && !isprint (sample[i]))
return (1);
}
return (0);
}
因此如果執行一個空檔案,將會看到什麼也不輸出,如果執行一個非linux可執行檔二進位檔案比如cer格式的二進位認證,那麼核心將返回ENOEXEC,接下來bash會嘗試執行之,然後最終返回一個EX_BINARY_FILE錯誤,列印“cannot execute binary file”,如果執行一個檔案頭不是#!的文字檔,同樣核心會返回一個ENOEXEC錯誤,但是bash卻可以在shell_execve的後續執行中成功直接執行,可是如果在一個二進位檔案的頭部增加80位元組或者80位元組以上的文本,那麼根據shell_execve的邏輯,bash也會將之作為指令碼來分行執行,然後就會大錯特錯,因此雖然有的時候指令碼中不寫#!也能直接執行,但是要知道那可是bash本身幫你執行的,而不是核心直接執行的,起碼執行緒會先從核心錯誤返回,然後繼續shell_execve的後面的部分,確實稍微影響了效率,因此最好還是寫上標準的#!,況且有的shell並不會像bash這麼智能地重新解釋執行命令。