Shell本身是一個用C語言編寫的程式,它是使用者使用Linux的橋樑。Shell既是一種命令語言,又是一種程式設計語言。作為命令語言,它互動式地解釋和執行使用者輸入的命令;作為程式設計語言,它定義了各種變數和參數,並提供了許多在進階語言中才具有的控制結構,包括迴圈和分支。它雖然不是Linux系統核心的一部分,但它調用了系統核心的大部分功能來執行程式、建立檔案並以並行的方式協調各個程式的運行。因此,對於使用者來說,shell是最重要的公用程式,深入瞭解和熟練掌握shell的特性極其使用方法,是用好Linux系統的關鍵。可以說,shell使用的熟練程度反映了使用者對Linux使用的熟練程度。 一、什麼是shell 當一個使用者登入Linux系統之後,系統初始化程式init就為每一個使用者運行一個稱為shell(外殼)的程式。那麼,shell是什麼呢?確切一點說,shell就是一個命令列解譯器,它為使用者提供了一個向Linux核心發送請求以便運行程式的介面系統級程式,使用者可以用shell來啟動、掛起、停止甚至是編寫一些程式。 當使用者使用Linux時是通過命令來完成所需工作的。一個命令就是使用者和shell之間對話的一個基本單位,它是由多個字元組成並以換行結束的字串。shell解釋使用者輸入的命令,就象DOS裡的command.com所做的一樣,所不同的是,在DOS中,command.com只有一個,而在Linux下比較流行的shell有好幾個,每個shell都各有千秋。一般的Linux系統都將bash作為預設的shell。 二、幾種流行的shell 目前流行的shell有ash、bash、ksh、csh、zsh等,你可以用下面的命令來查看你自己的shell類型: #echo $SHELL $SHELL是一個環境變數,它記錄使用者所使用的shell類型。你可以用命令: #shell-name 來轉換到別的shell,這裡shell-name是你想要嘗試使用的shell的名稱,如ash等。這個命令為使用者又啟動了一個shell,這個shell在最初登入的那個shell之後,稱為下級的shell或子shell。使用命令: $exit 可以退出這個子shell。 使用不同的shell的原因在於它們各自都有自己的特點,下面作一個簡單的介紹: 1.ash ash shell是由Kenneth Almquist編寫的,Linux中佔用系統資源最少的一個小shell,它只包含24個內部命令,因而使用起來很不方便。 2.bash bash是Linux系統預設使用的shell,它由Brian Fox和Chet Ramey共同完成,是Bourne Again Shell的縮寫,內部命令一共有40個。Linux使用它作為預設的shell是因為它有諸如以下的特色: (1)可以使用類似DOS下面的doskey的功能,用方向鍵查閱和快速輸入並修改命令。 (2)自動通過尋找匹配的方式給出以某字串開頭的命令。 (3)包含了自身的協助功能,你只要在提示符下面鍵入help就可以得到相關的協助。 3.ksh ksh是Korn shell的縮寫,由Eric Gisin編寫,共有42條內部命令。該shell最大的優點是幾乎和商業發行版的ksh完全相容,這樣就可以在不用花錢購買商業版本的情況下嘗試商業版本的效能了。 4.csh csh是Linux比較大的核心,它由以William Joy為代表的共計47位作者編成,共有52個內部命令。該shell其實是指向/bin/tcsh這樣的一個shell,也就是說,csh其實就是tcsh。 5.zsh zsh是Linux最大的shell之一,由Paul Falstad完成,共有84個內部命令。如果只是一般的用途,是沒有必要安裝這樣的shell的。 3. shell程式設計(基礎部分) 其實作為命令語言互動式地解釋和執行使用者輸入的命令只是shell功能的一個方面,shell還可以用來進行程式設計,它提供了定義變數和參數的手段以及豐富的程式控制結構。使用shell編程類似於DOS中的批次檔,稱為shell script,又叫shell程式或shell命令檔案。 1.shell基本文法 shell的基本文法主要就是如何輸入命令運行程式以及如何在程式之間通過shell的一些參數提供便利手段來進行通訊。 (1)輸入輸出重新導向 在Linux中,每一個進程都有三個特殊的檔案描述指標:標準輸入(standard input,檔案描述指標為0)、標準輸出(standard output,檔案描述指標為1)、標準錯誤輸出(standard error,檔案描述指標為2)。這三個特殊的檔案描述指標使進程在一般情況下接收標準輸入終端的輸入,同時由標準終端來顯示輸出,Linux同時也向使用者提供可以使用普通的檔案或管道來取代這些標準輸入輸出裝置。在shell中,使用者可以利用“>”和“<”來進行輸入輸出重新導向。如: command>file:將命令的輸出結果重新導向到一個檔案。 command>&file:將命令的標準錯誤輸出一起重新導向到一個檔案。 command>>file:將標準輸出的結果追加到檔案中。 command>>&file:將標準輸出和標準錯誤輸出的結構都追加到檔案中。 command (2)管道pipe pipe同樣可以在標準輸入輸出和標準錯誤輸出間做代替工作,這樣一來,可以將某一個程式的輸出送到另一個程式的輸入,其文法如下: command1| command2[| command3...] 也可以連同標準錯誤輸出一起送入管道: command1| &command2[|& command3...] (3)前台和後台 在shell下面,一個新產生的進程可以通過用命令後面的符號“;”和“&”來分別以前台和背景方式來執行,文法如下: command 產生一個前台的進程,下一個命令須等該命令運行結束後才能輸入。 command & 產生一個背景進程,此進程在後台啟動並執行同時,可以輸入其他的命令。 2.shell程式的變數和參數 像進階程式設計語言一樣,shell也提供說明和使用變數的功能。對shell來講,所有變數的取值都是一個字串,shell程式採用$var的形式來引用名為var的變數的值。 Shell有以下幾種基本類型的變數: (1)shell定義的環境變數 shell在開始執行時就已經定義了一些和系統的工作環境有關的變數,這些變數使用者還可以重新定義,常用的shell環境變數有: HOME:用於儲存註冊目錄的完全路徑名。 PATH:用於儲存用冒號分隔的目錄路徑名,shell將按PATH變數中給出的順序搜尋這些目錄,找到的第一個與命令名稱一致的可執行檔將被執行。 TERM:終端的類型。 UID:目前使用者的標識符,取值是由數字構成的字串。 PWD:當前工作目錄的絕對路徑名,該變數的取值隨cd命令的使用而變化。 PS1:主提示符,在特權使用者下,預設的主提示符是“#”,在普通使用者下,預設的主提示符是“$”。 PS2:在shell接收使用者輸入命令的過程中,如果使用者在輸入行的末尾輸入“/”然後斷行符號,或者當使用者按斷行符號鍵時shell判斷出使用者輸入的命令沒有結束時,顯示這個輔助提示符,提示使用者繼續輸入命令的其餘部分,預設的輔助提示符是“>”。 (2)使用者定義的變數 使用者可以按照下面的文法規則定義自己的變數: 變數名=變數值 要注意的一點是,在定義變數時,變數名前不應加符號“$”,在引用變數的內容時則應在變數名前加“$”;在給變數賦值時,等號兩邊一定不能留空格,若變數中本身就包含了空格,則整個字串都要用雙引號括起來。 在編寫shell程式時,為了使變數名和命令名相區別,建議所有的變數名都用大寫字母來表示。 有時我們想要在說明一個變數並對它設定為一個特定值後就不在改變它的值,這可以用下面的命令來保證一個變數的唯讀性: readly 變數名 在任何時候,建立的變數都只是當前shell的局部變數,所以不能被shell啟動並執行其他命令或shell程式所利用,export命令可以將一局部變數提供給shell執行的其他命令使用,其格式為: export 變數名 也可以在給變數賦值的同時使用export命令: export 變數名=變數值 使用export說明的變數,在shell以後啟動並執行所有命令或程式中都可以訪問到。 (3)位置參數 位置參數是一種在調用shell程式的命令列中按照各自的位置決定的變數,是在程式名之後輸入的參數。位置參數之間用空格分隔,shell取第一個位置參數替換程式檔案中的$1,第二個替換$2,依次類推。$0是一個特殊的變數,它的內容是當前這個shell程式的檔案名稱,所以,$0不是一個位置參數,在顯示當前所有的位置參數時是不包括$0的。 (4)預定義變數 預定義變數和環境變數相類似,也是在shell一開始時就定義了的變數,所不同的是,使用者只能根據shell的定義來使用這些變數,而不能重定義它。所有預定義變數都是由$符和另一個符號組成的,常用的shell預定義變數有: $#:位置參數的數量 $*:所有位置參數的內容 $?:命令執行後返回的狀態 $$:當前進程的進程號 $!:後台啟動並執行最後一個進程號 $0:當前執行的進程名 其中,“$?”用於檢查上一個命令執行是否正確(在Linux中,命令退出狀態為0表示該命令正確執行,任何非0值表示命令出錯)。 “$$”變數最常見的用途是用作臨時檔案的名字以保證臨時檔案不會重複。 (5)參數置換的變數 shell提供了參數置換能力以便使用者可以根據不同的條件來給變數賦不同的值。參數置換的變數有四種,這些變數通常與某一個位置參數相聯絡,根據指定的位置參數是否已經設定類決定變數的取值,它們的文法和功能分別如下。 a. 變數=${參數-word}:如果設定了參數,則用參數的值置換變數的值,否則用word置換。即這種變數的值等於某一個參數的值,如果該參數沒有設定,則變數就等於word的值。 b. 變數=${參數=word}:如果設定了參數,則用參數的值置換變數的值,否則把變數設定成word然後再用word替換參數的值。注意,位置參數不能用於這種方式,因為在shell程式中不能為位置參數賦值。 c. 變數=${參數?word}:如果設定了參數,則用參數的值置換變數的值,否則就顯示word並從shell中退出,如果省略了word,則顯示標準資訊。這種變數要求一定等於某一個參數的值,如果該參數沒有設定,就顯示一個資訊,然後退出,因此這種方式常用於出錯指示。 d. 變數=${參數+word}:如果設定了參數,則用word置換變數,否則不進行置換。 所有這四種形式中的“參數”既可以是位置參數,也可以是另一個變數,只是用位置參數的情況比較多。 接下來以bash為例向大家介紹shell程式設計的進階部分:shell編程的流程式控制制、調試方法及shell程式的運行方法,順便也向大家介紹一下bash的內部命令。 四、shell程式設計的流程式控制制 和其他進階程式設計語言一樣,shell提供了用來控製程序執行流程的命令,包括條件分支和迴圈結構,使用者可以用這些命令建立非常複雜的程式。 與傳統的語言不同的是,shell用於指定條件值的不是布林運算式而是命令和字串。 1.test測試命令 test命令用於檢查某個條件是否成立,它可以進行數值、字元和檔案三個方面的測試,其測試符和相應的功能分別如下: (1)數值測試: -eq:等於則為真 -ne:不等於則為真 -gt:大於則為真 -ge:大於等於則為真 -lt:小於則為真 -le:小於等於則為真 (2)字串測試: =:等於則為真 !=:不相等則為真 -z字串:字串長度偽則為真 -n字串:字串長度不偽則為真 (3)檔案測試: -e檔案名稱:如果檔案存在則為真 -r檔案名稱:如果檔案存在且可讀則為真 -w檔案名稱:如果檔案存在且可寫則為真 -x檔案名稱:如果檔案存在且可執行則為真 -s檔案名稱:如果檔案存在且至少有一個字元則為真 -d檔案名稱:如果檔案存在且為目錄則為真 -f檔案名稱:如果檔案存在且為普通檔案則為真 -c檔案名稱:如果檔案存在且為字元型特殊檔案則為真 -b檔案名稱:如果檔案存在且為塊特殊檔案則為真 另外,Linux還提供了與(“!”)、或(“-o)、非(“-a”)三個邏輯操作符用於將測試條件串連起來,其優先順序為:“!”最高,“-a”次之,“-o”最低。 同時,bash也能完成簡單的算術運算,格式如下: $[expression] 例如:var1=2 var2=$[var1*10+1] 則:var2的值為21。 2.if條件陳述式 shell程式中的條件分支是通過if條件陳述式來實現的,其一般格式為: if條件命令串 then 條件為真時的命令串 else 條件為假時的命令串 fi 3.for迴圈 for迴圈對一個變數的可能的值都執行一個命令序列。賦給變數的幾個數值既可以在程式內以數值列表的形式提供,也可以在程式以外以位置參數的形式提供。for迴圈的一般格式為: for變數名 [in數值列表] do 若干個命令列 done 變數名可以是使用者選擇的任何字串,如果變數名是var,則在in之後給出的數值將順序替換迴圈命令列表中的$var。如果省略了in,則變數var的取值將是位置參數。對變數的每一個可能的賦值都將執行do和done之間的命令列表。 4.while和until迴圈 while和until命令都是用命令的返回狀態值來控制迴圈的。While迴圈的一般格式為: while 若干個命令列1 do 若干個命令列2 done 只要while的“若干個命令列1”中最後一個命令的返回狀態為真,while迴圈就繼續執行do...done之間的“若干個命令列2”。 until命令是另一種迴圈結構,它和while命令相似,其格式如下: until 若干個命令列1 do 若干個命令列2 done until迴圈和while迴圈的區別在於:while迴圈在條件為真時繼續執行迴圈,而until則是在條件為假時繼續執行迴圈。 Shell還提供了true和false兩條命令用於建立無限迴圈結構的需要,它們的返回狀態分別是總為0或總為非0 5.case條件選擇 if條件陳述式用於在兩個選項中選定一項,而case條件選擇為使用者提供了根據字串或變數的值從多個選項中選擇一項的方法,其格式如下: case string in exp-1) 若干個命令列1 ;; exp-2) 若干個命令列2 ;; …… *) 其他命令列 esac shell通過計算字串string的值,將其結果依次和運算式exp-1、exp-2等進行比較,直到找到一個匹配的運算式為止,如果找到了匹配項則執行它下面的命令直到遇到一對分號(;;)為止。 在case運算式中也可以使用shell的萬用字元(“*”、“?”、“[ ]”)。通常用“*”作為case命令的最後運算式以便使在前面找不到任何相應的匹配項時執行“其他命令列”的命令。 6.無條件控制語句break和continue break用於立即終止當前迴圈的執行,而contiune用於不執行迴圈中後面的語句而立即開始下一個迴圈的執行。這兩個語句只有放在do和done之間才有效。 7.函數定義 在shell中還可以定義函數。函數實際上也是由若干條shell命令組成的,因此它與shell程式形式上是相似的,不同的是它不是一個單獨的進程,而是shell程式的一部分。函數定義的基本格式為: functionname { 若干命令列 } 調用函數的格式為: functionname param1 param2…… shell函數可以完成某些例行的工作,而且還可以有自己的退出狀態,因此函數也可以作為if、while等控制結構的條件。 在函數定義時不用帶參數說明,但在調用函數時可以帶有參數,此時shell將把這些參數分別賦予相應的位置參數$1、$2、...及$*。 9.訊號 trap命令用於在shell程式中捕捉到訊號,之後可以有三種反應方式: (1)執行一段程式來處理這一訊號 (2)接受訊號的預設操作 (3)忽視這一訊號 trap對上面三種方式提供了三種基本形式: 第一種形式的trap命令在shell接收到signal list清單中數值相同的訊號時,將執行雙引號中的命令串。 trap 'commands' signal-list trap "commands" signal-list 為了恢複訊號的預設操作,使用第二種形式的trap命令: trap signal-list 第三種形式的trap命令允許忽視訊號: trap " " signal-list 注意: (1)對訊號11(段違例)不能捕捉,因為shell本身需要捕捉該訊號去進行記憶體的轉儲。 (2)在trap中可以定義對訊號0的處理(實際上沒有這個訊號),shell程式在其終止(如執行exit語句)時發出該訊號。 (3)在捕捉到signal-list中指定的訊號並執行完相應的命令之後,如果這些命令沒有將shell程式終止的話,shell程式將繼續執行收到訊號時所執行的命令後面的命令,這樣將很容易導致shell程式無法終止。 另外,在trap語句中,單引號和雙引號是不同的,當shell程式第一次碰到trap語句時,將把commands中的命令掃描一遍。此時若commands是用單引號括起來的話,那麼shell不會對commands中的變數和命令進行替換,否則commands中的變數和命令將用當時具體的值來替換。 五、運行shell程式的方法 使用者可以用任何編輯程式來編寫shell程式。因為shell程式是解釋執行的,所以不需要編譯裝配成目標程式,按照shell編程的慣例,以bash為例,程式的第一行一般為“#!/bin/bash”,其中#表示該行是注釋,歎號“!”告訴shell運行歎號之後的命令並用檔案的其餘部分作為輸入,也就是運行/bin/bash並讓/bin/bash去執行shell程式的內容。 執行shell程式的方法有三種: (1)sh shell程式檔案名稱 這種方法的命令格式為: bash shell程式檔案名稱 這實際上是調用一個新的bash命令解釋程式,而把shell程式檔案名稱作為參數傳遞給它。新啟動的shell將去讀指定的檔案,執行檔案中列出的命令,當所有的命令都執行完結束。該方法的優點是可以利用shell調試功能。 (3)用chmod命令使shell程式成為可執行檔 一個檔案能否運行取決於該檔案的內容本身可執行且該檔案具有執行權。對於shell程式,當用編輯器產生一個檔案時,系統賦予的許可許可權都是644(rw-r-r--),因此,當使用者需要運行這個檔案時,只需要直接鍵入檔案名稱即可。 在這三種運行shell程式的方法中,最好按下面的方式選擇:當剛建立一個shell程式,對它的正確性還沒有把握時,應當使用第一種方式進行調試。當一個shell程式已經調試好時,應使用第三種方式把它固定下來,以後只要鍵入相應的檔案名稱即可,並可被另一個程式所調用。 |