Linux系統程式設計(12)——shell基礎

尹成發表於2014-07-25

 

Shell的作用是解釋執行使用者的命令,使用者輸入一條命令,Shell就解釋執行一條,這種方式稱為互動式(Interactive),Shell還有一種執行命令的方式稱為批處理(Batch),使用者事先寫一個Shell指令碼(Script),其中有很多條命令,讓Shell一次把這些命令執行完,而不必一條一條地敲命令。Shell指令碼和程式語言很相似,也有變數和流程控制語句,但Shell指令碼是解釋執行的,不需要編譯,Shell程式從指令碼中一行一行讀取並執行這些命令,相當於一個使用者把指令碼中的命令一行一行敲到Shell提示符下執行。

 

由於歷史原因,UNIX系統上有很多種Shell:

sh(Bourne Shell):由SteveBourne開發,各種UNIX系統都配有sh。

csh(C Shell):由Bill Joy開發,隨BSD UNIX釋出,它的流程控制語句很像C語言,支援很多BourneShell所不支援的功能:作業控制,命令歷史,命令列編輯。

ksh(Korn Shell):由David Korn開發,向後相容sh的功能,並且新增了csh引入的新功能,是目前很多UNIX系統標準配置的Shell,在這些系統上/bin/sh往往是指向/bin/ksh的符號連結。

tcsh(TENEX C Shell):是csh的增強版本,引入了命令補全等功能,在FreeBSD、Mac OS X等系統上替代了csh。

bash(Bourne Again Shell):由GNU開發的Shell,主要目標是與POSIX標準保持一致,同時兼顧對sh的相容,bash從csh和ksh借鑑了很多功能,是各種Linux發行版標準配置的Shell,在Linux系統上/bin/sh往往是指向/bin/bash的符號連結[36]。雖然如此,bash和sh還是有很多不同的,一方面,bash擴充套件了一些命令和引數,另一方面,bash並不完全和sh相容,有些行為並不一致,所以bash需要模擬sh的行為:當我們通過sh這個程式名啟動bash時,bash可以假裝自己是sh,不認擴充套件的命令,並且行為與sh保持一致。

 

使用者在命令列輸入命令後,一般情況下Shell會fork並exec該命令,但是Shell的內建命令例外,執行內建命令相當於呼叫Shell程式中的一個函式,並不建立新的程式。以前學過的cd、alias、umask、exit等命令即是內建命令,凡是用which命令查不到程式檔案所在位置的命令都是內建命令,內建命令沒有單獨的man手冊,要在man手冊中檢視內建命令,應該

$ man bash-builtins

 

內建命令雖然不建立新的程式,但也會有Exit Status,通常也用0表示成功非零表示失敗,雖然內建命令不建立新的程式,但執行結束後也會有一個狀態碼,也可以用特殊變數$?讀出。

 

首先編寫一個簡單的指令碼,儲存為script.sh:

#! /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


 

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

 

 

1、互動Shell(bash)fork/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下逐行執行指令碼中的命令。

 

 

 

 

相關文章