用戶在命令行輸入命令後,一般情況下Shell會fork
並exec
該命令,但是Shell的內建命令例外,執行內建命令相當於調用Shell進程中的一個函數,並不創建新的進程。以前學過的cd
、alias
、umask
、exit
等命令即是內建命令,凡是用which
命令查不到程序檔案所在位置的命令都是內建命令,內建命令沒有單獨的man手冊,要在man手冊中查看內建命令,應該
$ man bash-builtins
本節會介紹很多內建命令,如export
、shift
、if
、eval
、[
、for
、while
等等。內建命令雖然不創建新的進程,但也會有Exit Status,通常也用0表示成功非零表示失敗,雖然內建命令不創建新的進程,但執行結束後也會有一個狀態碼,也可以用特殊變數$?
讀出。
1、在完成第 5 節 “練習:實現簡單的Shell”時也許有的讀者已經試過了,在自己實現的Shell中不能執行cd
命令,因為cd
是一個內建命令,沒有程序檔案,不能用exec
執行。現在請完善該程序,實現cd
命令的功能,用chdir(2)
函數可以改變進程的當前工作目錄。
2、思考一下,為什麼cd
命令要實現成內建命令?可不可以實現一個獨立的cd
程序,例如/bin/cd
,就像/bin/ls
一樣?
首先編寫一個簡單的腳本,保存為script.sh
:
Shell腳本中用#
表示註釋,相當於C語言的//
註釋。但如果#
位於第一行開頭,並且是#!
(稱為Shebang)則例外,它表示該腳本使用後面指定的解釋器/bin/sh
解釋執行。如果把這個腳本檔案加上可執行權限然後執行:
$ chmod +x script.sh $ ./script.sh
Shell會fork
一個子進程並調用exec
執行./script.sh
這個程序,exec
系統調用應該把子進程的代碼段替換成./script.sh
程序的代碼段,並從它的_start
開始執行。然而script.sh
是個文本檔案,根本沒有代碼段和_start
函數,怎麼辦呢?其實exec
還有另外一種機制,如果要執行的是一個文本檔案,並且第一行用Shebang指定瞭解釋器,則用解釋器程序的代碼段替換當前進程,並且從解釋器的_start
開始執行,而這個文本檔案被當作命令行參數傳給解釋器。因此,執行上述腳本相當於執行程序
$ /bin/sh ./script.sh
以這種方式執行不需要script.sh
檔案具有可執行權限。再舉個例子,比如某個sed
腳本的檔案名是script
,它的開頭是
#! /bin/sed -f
執行./script
相當於執行程序
$ /bin/sed -f ./script.sh
以上介紹了兩種執行Shell腳本的方法:
$ ./script.sh $ sh ./script.sh
這兩種方法本質上是一樣的,執行上述腳本的步驟為:
交互Shell(bash
)fork
/exec
一個子Shell(sh
)用於執行腳本,父進程bash
等待子進程sh
終止。
sh
讀取腳本中的cd ..
命令,調用相應的函數執行內建命令,改變當前工作目錄為上一級目錄。
sh
讀取腳本中的ls
命令,fork
/exec
這個程序,列出當前工作目錄下的檔案,sh
等待ls
終止。
ls
終止後,sh
繼續執行,讀到腳本檔案末尾,sh
終止。
sh
終止後,bash
繼續執行,打印提示符等待用戶輸入。
如果將命令行下輸入的命令用()括號括起來,那麼也會fork
出一個子Shell執行小括號中的命令,一行中可以輸入由分號;隔開的多個命令,比如:
$ (cd ..;ls -l)
和上面兩種方法執行Shell腳本的效果是相同的,cd ..
命令改變的是子Shell的PWD
,而不會影響到互動式Shell。然而命令
$ cd ..;ls -l
則有不同的效果,cd ..
命令是直接在互動式Shell下執行的,改變互動式Shell的PWD
,然而這種方式相當於這樣執行Shell腳本:
$ source ./script.sh
或者
$ . ./script.sh
source
或者.
命令是Shell的內建命令,這種方式也不會創建子Shell,而是直接在互動式Shell下逐行執行腳本中的命令。