2. Shell如何執行命令

2.1. 執行互動式命令

用戶在命令行輸入命令後,一般情況下Shell會forkexec該命令,但是Shell的內建命令例外,執行內建命令相當於調用Shell進程中的一個函數,並不創建新的進程。以前學過的cdaliasumaskexit等命令即是內建命令,凡是用which命令查不到程序檔案所在位置的命令都是內建命令,內建命令沒有單獨的man手冊,要在man手冊中查看內建命令,應該

$ man bash-builtins

本節會介紹很多內建命令,如exportshiftifeval[forwhile等等。內建命令雖然不創建新的進程,但也會有Exit Status,通常也用0表示成功非零表示失敗,雖然內建命令不創建新的進程,但執行結束後也會有一個狀態碼,也可以用特殊變數$?讀出。

習題

1、在完成第 5 節 “練習:實現簡單的Shell”時也許有的讀者已經試過了,在自己實現的Shell中不能執行cd命令,因為cd是一個內建命令,沒有程序檔案,不能用exec執行。現在請完善該程序,實現cd命令的功能,用chdir(2)函數可以改變進程的當前工作目錄。

2、思考一下,為什麼cd命令要實現成內建命令?可不可以實現一個獨立的cd程序,例如/bin/cd,就像/bin/ls一樣?

2.2. 執行腳本

首先編寫一個簡單的腳本,保存為script.sh

例 31.1. 簡單的Shell腳本

#! /bin/sh

cd ..
ls

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

這兩種方法本質上是一樣的,執行上述腳本的步驟為:

圖 31.1. Shell腳本的執行過程

Shell腳本的執行過程

  1. 交互Shell(bashfork/exec一個子Shell(sh)用於執行腳本,父進程bash等待子進程sh終止。

  2. sh讀取腳本中的cd ..命令,調用相應的函數執行內建命令,改變當前工作目錄為上一級目錄。

  3. sh讀取腳本中的ls命令,fork/exec這個程序,列出當前工作目錄下的檔案,sh等待ls終止。

  4. ls終止後,sh繼續執行,讀到腳本檔案末尾,sh終止。

  5. 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下逐行執行腳本中的命令。

習題

1、解釋如下命令的執行過程:

$ (exit 2)
$ echo $?
2