next up previous
Next: 自以為已經知道所有答案的人可能會問的高級問題 Up: 中級的問題 Previous: 我要如何在檔案名字中加入日期?

為什麼有一些 script 是用 #! ... 做為檔案的開端?

我想有些人可能會把兩種以 '#' 這個字元開始的機制搞混了。這兩種機制 用來解決不同情況下的同一問題。

背景知識:當 UNIX 的 kernel 開始要執行一隻程式(使用 exec() 中的任一 個),會先偷看檔案開頭的 16 個 bit。這 16 個 bit 稱為 'magic number'。 Magic number 有幾個重要的功能。首先,kernel 再執行一個檔案之前會先 看看它的 magic number,如果 kernel 不認得那個 magic number,就不會 去執行之並且會 return 回 ENOEXEC。

再者,隨著時代進步, magic number 不只可以用來辨別是否為執行檔,更 可以用來辨別該如何執行此檔。舉例來說,假如你在 SCO XENIX/386 上  compile 了一個程式,然後將這個程式拿到 SysV/386 UNIX 上去執行, SysV/386 UNIX 的 kernel 會認得它的 magic number,說「啊哈!這是一個  x.out 格式的可執行檔」,然後會設定自己去使用 XENIX 相容的 system call。

既然 kernel 只能執行 binary executable image。你或許會問,那 script 又是怎麼執行的呢?當我在 shell prompt 下打 'my.script' 並不會得到 ENOEXEC啊!這是因為 script 的執行是由 shell 做的而非由 kernel 來做 的。在 shell 中執行程式的部份可能是長得這個樣子:

	        /* try to run the program */
	        execl(program, basename(program), (char *)0);

	        /* the exec failed -- maybe it is a shell script? */
	        if (errno == ENOEXEC)
	            execl ("/bin/sh", "sh", "-c", program, (char *)0);

	        /* oh no mr bill!! */
	        perror(program);
	        return -1;

	            (This example is highly simplified.  There is a lot
	            more involved, but this illustrates the point I'm
	            trying to make.)
在上例中,若第一個 execl() 成功的話,那在 execl() 底下的的部份就不必 看了,因為 execl() 執行後就不再回頭了。

假如第一個 execl() 失敗的話,那就表示這不是一個 binary executable, shell 會試著把它當成 shell script 來執行。

在柏克萊的人們對於如何擴充 kernel 執行程式的能力想到的了一個非常棒 的法子。他們讓 kernel 認得 '#!' 這個 magic number。(兩個 8-bit 的字元 構成一個 16 bits 的 magic numbre。)如果一個檔案是以 '#!' 開始的,則 kernel 會把第一行其它的內容當成要用來執行此檔案內容的命令。有了這個 處理後,我們就有了如下的作法:

	        #! /bin/sh

	        #! /bin/csh

	        #! /bin/awk -F:
這種處理只有存在於 Berkeley 系統,USG 系統的是直到 SVR4 才把這種 作法加入 kernel 中。所以若你用 System V 在 R4 之前的版本,除非廠商 有做修改,否則就只能執行 binary executable image。

講到這裡讓我們先把時光倒流,回到 USG based UNIX 還沒有 csh 的年 代。因為,有愈來愈多的的使用者說:「作為一個 interactive user interface 而言 sh 真的是一個失敗之作,我要用 csh 啦!」所以呢,有許 多的廠商就把 csh 與其 magic number 加入他們的產品中。

這種做法造成了一個問題。這麼說吧,你把 login shell 換成了 /bin/csh。 更進一步的假設你是個堅持要寫 csh script 的白癡笨蛋。你當然會希望只要 打 'my.script' 就能執行一個 csh script。而且希望不必經過 /bin/sh 而是 經由如下的作法執行:

	        execl ("/bin/csh", "csh", "-c", "my.script", (char *)0);

但是如過這麼做的話會有大麻煩的。想想原先系統裡還有不少 sh 的 shell sript,一旦用 csh 來執行這些 sript,那是必死無疑。所以得有一個可以有 時候用 csh,有時候用 sh 來執行 script 的方法。

當時想到的作法是讓 csh 去檢查要執行的 script 的第一個字元。假若這個 字元是 '#' 那 csh 會用 /bin/csh 去執行這個 script,否則就用 /bin/sh 去執行這個 script。上述的作法可能長得像這個樣子:

	        /* try to run the program */
	        execl(program, basename(program), (char *)0);

	        /* the exec failed -- maybe it is a shell script? */
	        if (errno == ENOEXEC && (fp = fopen(program, "r")) != NULL) {
        	    i = getc(fp);
	            (void) fclose(fp);
	            if (i == '#')
	                execl ("/bin/csh", "csh", "-c", program, (char *)0);
	            else
        	        execl ("/bin/sh", "sh", "-c", program, (char *)0);
	        }

	        /* oh no mr bill!! */
	        perror(program);
	        return -1;
有兩點要注意的是。第一,這是由 'csh' 動手腳,沒有動到 kernel 也沒有 改到別的 shell。所以如果要 execl()一個 script,不管它是不是以 '#' 開 始,你都會得到的 return value 都會是ENOEXEC。假如你從 csh 以外的  shell (如 /bin/sh ) 的 script,執行這個 script 的還是 sh 而非 csh。

第二,這個做法只判斷第一個字元是否為 '#',所以只要不是 '#' 其他不管 是啥東西都送給 sh 去處理。底下是幾個例子:

	        :

	        : /bin/sh

	                        <--- a blank line

	        : /usr/games/rogue

	        echo "Gee...I wonder what shell I am running under???"

同裡只要是以 '#' 為開端的 script,如下:

	        #

	        # /bin/csh

	        #! /bin/csh

	        #! /bin/sh

	        # Gee...I wonder what shell I am running under???
如果是在 csh 裡執行之,則會用 /bin/csh 來執行。

(註解:如果你用的 ksh,那把上述的 'sh' 換成 'ksh' 即可。因為, 理論上來說 Korn shell 應該是與 Bourne shell 相容的。如果你用的 是 zsh, bash 等其它的 shell,那自己看著辦吧。)

如果你的 kernel 有支援 '#!' 那 '#' 顯然就是多餘的做法。而且,還會造成 混淆,譬如當你用 '#! /bin/sh' 應該要怎麼辦呢?

還好,'#!' 日漸盛行。System V Release 4 已經將其加入。而有一些 System V Release 3.2 的廠商,也正把 V.4 中這類比較顯而易見的特性加入產品中, 並且嘗試著說服你說這些特性就夠了,你並不需要其他如一個真正 streams 或是動態調節 kernel 參數這類的東西。

XENIX 並不支援 '#!'。不過 XENIX 的 /bin/csh 有支援 '#' 的作法。 如 果 XENIX 支援 '#!' 當然不錯,可是我對此不抱任何希望。


next up previous
Next: 自以為已經知道所有答案的人可能會問的高級問題 Up: 中級的問題 Previous: 我要如何在檔案名字中加入日期?
Tan Koan-Sin
1999-03-02