Unix系列shell程式編寫

helloxchen發表於2010-11-08

*Shell是什麼?

  任何發明都具有供使用者使用的介面。UNIX供使用者使用的介面就是Shell(DOS的command熟悉吧,但UNIX的要強大的多)。
Shell為使用者提供了輸入命令和引數並可得到命令執行結果的環境。

  為了不同的需要,UNIX提供了不同的Shell。現在的UNIX大部分都支援BourneShell,以下教程就以BourneShell(Bsh)為例,一步步的領略UNIX
Shell的強大功能,佔先其強大魅力,達到更方便靈活的管理、應用UNIX的目的。

  1.UNIX核心和Shell的互動方法

  啟動UNIX時,程式UNIX(核心)將被調入計算機記憶體,並一直保留在記憶體中直到機器關閉。在引導過程中,程式
init將進入後臺執行一直到機器關閉。該程式查詢檔案/etc/inittab,該檔案列出了連線終端的各個埠及其特徵。當發現一個活動的終端時,init程式呼叫getty程式在終端上顯示login等登陸資訊。(username和passwd),在輸入密碼後,
getty呼叫login程式,該程式根據檔案/etc/passwd的內容來驗證使用者的身份。若使用者透過身份驗證,login程式
把使用者的home目錄設定成當前目錄並把控制交給一系列setup程式。setup程式可以是指定的應用程式,通常setup程式
為一個Shell程式,如:/bin/sh 即Bourne Shell(command出來了,呵呵)。

  得到控制後,Shell程式讀取並執行檔案/etc/.profile以及.profile。這兩個檔案分別建立了系統範圍內的和
該使用者自己的工作環境。最後Shell顯示命令提示符,如$。(這是以bsh為例,若是csh,為.cshrc,ksh為.kshrc,bash為.bashrc等等)
   

  注不妨把/etc/.profile和.profile看成DOS的autoexec.bat 或
config.sys檔案)

  當shell退出時,核心把控制交給init程式,該程式重新啟動自動登陸過程。有兩種方法使shell退出,一是使用者執行exit命令,二是
核心(例如root用kill命令)發出一個kill命令結束shell程式。shell退出後,核心回收使用者及程式使用的資源。

  使用者登陸後,使用者命令同計算機互動的關係為:命令程式---&gtShell程式---&gtUNIX核心---&gt計算機硬體。當使用者輸入一個命令,如$ls,
Shell將定位其可執行檔案/bin/ls並把其傳遞給核心執行。核心產生一個新的子程式呼叫並執行/bin/ls。當程式執行完畢後,核心取消
該子程式並把控制交給其父程式,即Shell程式。例如執行:

    $ps

    該命令將會列出使用者正在執行的程式,即Shell程式(下來詳細說說,別急現在)和ps程式。若執行:

    $sleep 10 &
    $ps

  其中第一條命令將產生一個在後臺執行的sleep子程式。ps命令執行時會顯示出該子程式。

  每當使用者執行一條命令時,就會產生一個子程式。該子程式的執行與其父程式或Shell完全無關,這樣可以使Shell去做其他工作。(Shell只是把使用者的意圖告訴核心,然後該幹嘛幹嘛)
現在windows有個計劃任務(在固定的時間,日期自動執行某任務),其實UNIX很早就有這個功能了,也就是所謂的Shell的自動執行。一些UNIX
資源,如cron可以自動執行Shell程式而無需使用者的參與,(這個功能好象在/var/spool/crotab目錄裡)。

Crontab 程式對於系統管理員來說是非常有用的。Cron
服務用於計劃程式在特定時間(月、日、周、時、分)執行。我們以root的crontab 為例。根使用者的
crontab 檔案放在 /var/spool/crontab/root 中,其格式如下:

  (1)  (2)  (3)  (4)  (5)  (6)
   0   0   *   *   3   /usr/bin/updatedb
      1. 分鐘 (0-60)
      2. 小時 (0-23)
      3. 日 (1-31)
      4. 月 (1-12)
      5. 星期 (1-7)
      6. 所要執行的程式
  2.Shell的功能和特點
  1>命令列解釋
  2>使用保留字
  3>使用Shell元字元(萬用字元)
  4>可處理程式命令
  5>使用輸入輸出重定向和管道
  6>維護一些變數
  7>執行環境控制
  8>支援Shell程式設計

  對於"命令列解釋"就不多說了,就是在shell提示符(例如:"$","%","#"等)後輸入一行unix命令,Shell將接收使用者的輸入。

  "使用保留字":Shell有一些具有特殊意義的字,例如在Shell指令碼中,do,done,for等字用來控制迴圈操作,if,then等控制條件操作。
保留字隨Shell環境的不同而不同。

  "萬用字元":* 匹配任何位置
       ? 匹配單個字元
       [] 匹配的字元範圍或列表 例如:
       
          $ls [a-c]*
         
          將列出以a-c範圍內字元開頭的所有檔案
          $ls [a,m,t]*
         將列出以e,m或t開頭的所有檔案

  "程式命令"
:當使用者輸入命令後,Shell讀取環境變數$path(一般在使用者自己的.profile中設定),該變數包含了命令可執行檔案可能存在的目錄列表。
shell從這些目錄中尋找命令所對應的可執行檔案,然後將該檔案送給核心執行。

  "輸入輸出重定向及管道" :重定向的功能同DOS的重定向功能:

     ">" 重定向輸出
     "
  而管道符號,是unix功能強大的一個地方,符號是一條豎線:"|",用法:
command 1 | command 2 他的功能是把第一個命令command 1執行的結果作為command
2的輸入傳給command 2,例如:

    $ls -s|sort -nr|pg

  該命令列出當前目錄中的所有檔案,並把輸出送給sort命令作為輸入,sort命令按數字遞減的順序把ls的輸出排序。然後把排序後的
內容傳送給pg命令,pg命令在顯示器上顯示sort命令排序後的內容。

  "維護變數"
:Shell可以維護一些變數。變數中存放一些資料供以後使用。使用者可以用"="給變數賦值,如:

         $lookup=/usr/mydir

該命令建立一個名為lookup的變數並給其賦值/usr/mydir,以後使用者可以在命令列中使用lookup來代替/usr/mydir,例如:
         
         $echo $lookup
         結果顯示:/usr/mydir

         為了使變數能被子程式使用,可用exprot命令,例如:

         $lookup=/usr/mydir
         $export lookup

  "執行環境控制"
:當使用者登陸啟動shell後,shell要為使用者建立一個工作的環境,如下:

  1>當login程式啟用使用者shell後,將為使用者建立環境變數。從/etc/profile和.profile檔案中讀出,在這些檔案中一般都用$TERM
變數設定終端型別,用$PATH變數設定Shell尋找可執行檔案的路徑。

  2>從/etc/passwd檔案或命令列啟動shell時,使用者可以給shell程式指定一些引數,例如"-x",可以在命令執行前顯示該命令及其引數。後面詳細介紹這些引數。

  "shell程式設計" :本文主要介紹的內容。

  shell本身也是一種語言(*可以先理解為unix命令的組合,加上類C的條件,迴圈等程式控制語句,類似dos批處理,但要強大的多),使用者可以
透過shell程式設計(指令碼,文字檔案),完成特定的工作。

SHELL變數

  下面我們詳細的介紹Bourne Shell的程式設計:

  自從貝爾實驗室設計了Bourne
Shell。從那時起許多廠商根據不同的硬體平臺設計了許多版本得unix。但在眾多版本的unix中,Bourne Shell
一直保持一致。
  1>Bsh的啟動:使用者在登陸後,系統根據檔案/etc/passwd中有關該使用者的資訊項啟動Shell。例如某使用者在passwd中
的資訊項為:

    ice_walk:!:411:103:Imsnow ,ice_walk:/home/ice_walk:/bin/bsh

  則表明,使用者名稱是ice_walk等資訊,在最後一項"/bin/bsh"表明使用者的sh環境型別是bsh,於是系統啟動之。在啟動或執行(包括下面我們要講
的shell程式--指令碼)過程中可以使用以下一些引數,我們一一說明:

  -a 將所有變數輸出
  -c "string"從string中讀取命令
  -e 使用非互動式模式
  -f 禁止shell檔名產生
  -h 定義
  -i 互動式模式
  -k 為命令的執行設定選項
  -n 讀取命令但不執行
  -r 受限模式
  -s 命令從標準輸入讀取
  -t 執行一命令,然後退出shell
  -u 在替換時,使用未設定的變數將會出錯
  -v 顯示shell的輸入行
  -x 跟蹤模式,顯示執行的命令

許多模式可以組合起來用,您可以試試了,但-ei好象不行,你說why呢?

  使用set可以設定或取消shell的選項來改變shell環境。開啟選項用"-",關閉選項用"+",多數unix允許開啟或關閉a、f、e、h、k、n、
u、v和x選項。若顯示Shell中已經設定的選項,執行:

    $echo $-

  Bsh中每個使用者的home目錄下都有一個.profile檔案,可以修改該檔案來修改shell環境。為了增加一個可執行檔案的路徑(例如/ice_walk/bin),
可以把下面程式碼加入.profile中

    PATH=$PATH:/ice_walk/bin;exprot PATH

   .profile中shell的環境變數意思如下:

    CDPATH 執行cd命令時使用的搜尋路徑
    HOME 使用者的home目錄
    IFS 內部的域分割符,一般為空格符、製表符、或換行符
    MAIL 指定特定檔案(信箱)的路徑,有UNIX郵件系統使用
    PATH 尋找命令的搜尋路徑(同dos的config.sys的 path)
    PS1 主命令提示符,預設是"$"
    PS2 從命令提示符,預設是">"
    TERM 使用終端型別

  2>Bsh裡特殊字元及其含義

  在Bsh中有一組非字母字元。這些字元的用途分為四類:作為特殊變數名、產生檔名、資料或程式控制以及引用和逃逸字元控制。他們
可以讓使用者在Shell中使用最少的程式碼完成複雜的任務。

     *> Shell變數名使用的特殊字元
        $# 傳送給命令Shell的引數序號
        $- 在Shell啟動或使用set命令時提供選項
        $? 上一條命令執行後返回的值
        $$ 當前shell的程式號
        $! 上一個子程式的程式號
        $@ 所有的引數,每個都用雙括號括起
        $* 所有引數,用雙括號括起
        $n 位置引數值,n表示位置
        $0 當前shell名
     *>產生檔名的特殊字元
        包括"*","?","[]",上面講過,不再多說。
     *>資料或程式控制使用的特殊字元
        >(file) 輸出重定向到檔案中(沒有檔案則建立,有則覆蓋)
        >>(file)
輸出重定向到檔案中(沒有則建立,有則追加到檔案尾部)
                ; 命令分割符
        | 管道符
        & 後臺執行(例如:sleep 10 &)
        ` ` 命令替換,重定向一條命令的輸出作為另一命令的引數
     *>對於引用或逃逸的特殊字元

Bsh用單引號' '和雙引號"
"將特殊字元或由空白分隔的字引用起來組成一個簡單的資料串.使用單引號和雙引號的區別是雙引號中的內容可進行引數和變數替換.逃逸字元也一樣.

        $echo "$HOME $PATH"
         結果顯示$/u/ice_walk/bin:/etc:/usr/bin
        而$echo '$HOME $PATH' 結果顯示$HOME $PATH

  shell的逃逸符是一個"",表示其後的字元不具有特殊的含義或不是shell的函式

        $echo $HOME $PATH
        結果顯$$HOME /bin:/etc:/usr/bin:

3>Bsh的變數

  前面我們在多個地方引用了變數,當Shell遇到一個"$"符時(沒有被引用或逃逸),它將認為其後為一變數。不論該變數是環境變數還是使用者自定義的變數,在命令列中變數名要被變數值替換。例如命令:ls
$HOME將列出變數HOME對應目錄下的檔案。
使用者可以在命令列中的任何地方進行變數替換。包括命令名本身,例如:

    $dir=ls
    $$dir f*

  將列出以f開頭的檔案。

  現在詳細的介紹下Bsh的變數。Bsh中有四類變數:使用者定義的變數、位置變數(shell引數)、預定義變數及環境變數。

  使用者定義的變數:

  使用者定義的變數由字母和下劃線組成,並且變數名的第一個字元不能為數字(0~9)。與其他UNIX名字一樣,變數名是大小寫敏感的。使用者可以在命令列上用"="給變數賦值,例如:

    $NAME=ice_walk

  給變數NAME賦值為ice_walk,在應用變數NAME的時候,在NAME前加"$"即可,前面已說,不再廢話(別說我廢話多,關鍵是沒當過老師)。可以用變數和其他字元組成新的字,例如:

    $SUN=sun
    $echo ${SUN}day

  在應用shell變數時候,可以在變數名字兩邊$後面加上{},以更加清楚的顯示給shell,哪個是真正的變數,以實現字串的合併等功能。
 
  結果顯示:sunday(注意不能echo
$SUNday,因為SUNday變數沒定義,讀者試下執行結果)
使用者也可以在命令列上同時對多個變數賦值,賦值語句之間用空格分開:

    $X=x Y=y

    注意變數賦值是從右到左進行的

    $X=$Y Y=y
    X的值是y
    $X=z Y=$Z

    Y的值是空(變數未賦值時,shell不報錯,而是賦值為空)

  使用者可以使用"unset "命令清除給變數賦的值

使用者使用變數時要在其前面加一"$"符,使變數名被變數值所替換。Bsh可以進行變數的條件替換,即只有某種條件發生時才進行替換。替換條件放在一對大括號{}中,如:

    ${variable: -value}
variable是一變數值,value是變數替換使用的預設值

    $echo Hello $UNAME
    結果顯示:Hello
    $echo Hello ${UNAME: -there}
    結果顯示:Hello there
    $echo $UNAME
    結果顯示: (空)
    $UNAME=John
    $echo Hello ${UNAME: -there}
    結果顯示:Hello John

  可以看出,變數替換時將使用命令列中定義的預設值,但變數的值並沒有因此而改變。另外一種替換的方法是不但使用預設值進行替換,而且將預設值賦給該變數。其形式如下:

    ${variable:=value}

  該形式在變數替換後同時把值value符給變數variable。

    $echo Hello $UNAME
    結果顯示:Hello
    $echo Hello ${UNAME:=there}
    結果顯示:Hello there
    $echo $UNAME
    結果顯示:there
    $UNAME=John
    $echo Hello ${UNAME:-there}
    結果顯示:Hello John

  變數替換的值也可以是` `括起來的命令:

    $USERDIR={$Mydir: -`pwd`}

  第三種變數的替換方法是隻有當變數已賦值時才用指定值替換形式:

    ${variable: +value}

    只有變數variable已賦值時,其值才用value替換,否則不進行任何替換,例如:

    $ERROPT=A
    $echo ${ERROPT: +"Error tracking is acitive"}
    結果顯示:Error tracking is acitive
    $ERROPT=
    $echo ${ERROPT: +"Error tracking is acitive"}
    結果顯示: (空)

  我們還可以使用錯誤檢查的條件進行變數替換:

    ${variable:?message}

當變數variable已設定時,正常替換。否則訊息message將送到標準錯誤輸出(若此替換出現在shell程式中,那麼該程式將終止)。 例如:

    $UNAME=
    $echo $ {UNAME:?"UNAME HAS NOT BEEN SET"}
    結果顯示:UNAME HAS NOT BEEN SET

    $UNAME=Stephanie
    $echo $ {UNAME:?"UNAME HAS NOT BEEN SET"}

    結果顯示:Stephanie
    當沒有指定message時,shell將顯示一條預設的訊息,例如:

    $UNAME=
    $echo $ {UNAME:?}
    結果顯示:sh:UNAME:parameter null or not set

4>位置變數或Shell引數

  在shell解釋使用者的命令時,將把命令列的第一個字作為命令,而其他的字作為引數。當命令對應的可執行檔案為Shell程式時,這些引數將作為位置變數傳送給該程式。第一個引數記為$1,第二個為$2....第九個為$9。其中1到9是真正的引數名,"$"符只是用來標識變數的替換。

  位置變數$0指命令對應的可執行檔名。在後面將詳細介紹位置變數。

  1.只讀變數

  使用者將變數賦值後,為了防止以後對該變數的修改,可以用以下命令將該變數設定為只讀變數:

    readonly variable

  2.export命令

  shell執行一個程式時,首先為該程式建立一個新的執行環境,稱為子shell。在Bourne
Shell中變數都是區域性的,即他們只在建立他們的Shell中有意義。使用者可以用export命令讓變數被其他子Shell識別。但某使用者的變數是沒法讓其他使用者使用的。

  當使用者啟動一個新shell時,該shell將使用預設的提示符。因為賦給變數PS1的值只在當前shell中有效。為了讓子Shell使用當前Shell中定義的提示符號,可以使用export命令:

    $PS1="Enter command:"
    Enter command:export PS1
    Enter command:sh
    Enter command:

    此時變數PS1變成了全域性變數。它可以被其子Shell使用。當變數被設定成全域性的以後,將一直保持有效直到使用者退出該變數所在的Shell。使用者可以在檔案.profile中給一個變數永久賦值。詳見"規範Shell"。

基本語句

  從本節起,我們將詳細介紹Shell程式設計的基本知識,透過編寫Shell指令碼,使用者可以根據自己的需要有條件的或者重複的執行命令。透過Shell程式,可以把單個的UNIX命令組合成一個完全實用的工具,完成使用者的任務。

  1>什麼是Shell程式

  當使用者在UNIX Shell中輸入了一條複雜的命令,如:

    $ls -R /|greo myname |pg

  我們可以稱使用者在對Shell程式設計,當把這條語句寫在一個檔案裡,並且符給該檔案可執行許可權,那麼該檔案就是我們傳統上說的Shell程式。

  2>簡單的Shell程式

  假設使用者每天使用下述命令備份自己的資料檔案:

    $cd /usr/icewalk;ls * |cpio -o > /dev/fd0

  我們可以把它寫在一個檔案,如:ba.sh中:

    $cat >ba.sh
    cd /usr/icewalk
    ls * |cpio -o > /dev/fd0
    ^D  (ctrl_d)

  程式ba.sh就是Shell指令碼,使用者可以用vi或其他編輯工具編寫更復雜的指令碼。

  此時使用者備份檔案只需要執行Shell程式ba.sh,執行時需在當前Shell中建立一個子Shell:

    $sh ba.sh

  程式sh與使用者登陸時執行的Bourne
Shell相同,但當Sh命令帶引數ba.sh後,它將不再是一個互動式的Shell,而是直接從檔案ba.sh中讀取命令。

  執行ba.sh中命令的另一方法是給檔案ba.sh執行許可權:

    $chmod +x ba.sh

  此時,使用者可以輸入檔名ba.sh做為一個命令來備份自己的資料,需要注意的是,用這種方法執行命令的時候,檔案ba.sh必須存在於環境變數$PATH所指定的路徑上。

3>在Shell中使用資料變數

  使用者可以在Shell中使用資料變數,例如ba.sh程式:

    cd/usr/icewalk
    ls|cpio -o > /dev/fd0

  該程式中要備份的目錄為一常量,即該程式只能用來備份一個目錄。若在該程式中使用變數,則會使其更通用:

    workdir=$1
    cd $workdir
    ls * |cpio -o > /dev/fd0

  透過這一改變,使用者可以使用程式備份變數$workdir指定的目錄。例如我們要備份/home/www的內容,只要執行ba.sh
/home/www即可實現。(若不明白
$1,下面將詳細介紹shell引數的傳遞,$1代表本sh程式-ba.sh的第一個引數)

  4>在Shell程式中加上註釋

  為了增加程式的可讀性,我們提倡加入註釋。在Shell程式中註釋將以"#"號開始。當Shell解釋到"#"時,會認為從"#"號起一直到該行行尾為註釋。

  5>對Shell變數進行算術運算

  高階語言中變數是具有型別的,即變數將被限制為某一資料型別,如整數或字元型別。Shell變數通常按字元進行儲存,為了對Shell變數進行算術運算,必須使用expr命令。

  expr命令將把一個算術表示式作為引數,通常形式如下:

    expr [數字] [運算子] [數字]

  由於Shell是按字元形式儲存變數的,所以使用者必須保證參加算術運算的運算元必須為數值。下面是有效的算術運算子:

    +   兩個整數相加
    -   第一個數減去第二個數
    *   兩整數相乘
    /   第一個整數除以第二個整數
    %   兩整數相除,取餘數
  例如:
    $expr 2 + 1
     結果顯示:3
    $expr 5 - 3
     結果顯示:2

若expr的一個引數是變數,那麼在表示式計算之前用變數值替換變數名。
    $int=3
    $expr $int + 4
    結果顯示:7
  使用者不能單純使用"*"做乘法,若輸入:
    $expr 4*5
  系統將會報錯,因為Shell看到"*"將會首先進行檔名替換。正確形式為:
    $expr 4 * 5
     結果顯示:20
  多個算術表示式可以組合在一起,例如:
    $expr 5 + 7 / 3
    結果顯示:7
  運算次序是先乘除後加減,若要改變運算次序,必須使用"`"號,如:
    $int=`expr 5 + 7`
    $expr $int/3
     結果顯示:4
    或者:
    $expr `expr 5+7`/3
    結果顯示:4

  6>向Shell程式傳遞引數

  一個程式可以使用兩種方法獲得輸入資料。一是執行時使用引數。另一種方法是互動式地獲得資料。vi編輯程式可以透過互動式的方法獲得資料,而ls和 expr則從引數中取得資料。以上兩種方法Shell程式都可以使用。在"互動式讀入資料"一節中將介紹Shell程式透過互動式的方法獲得引數。

  透過命令列給Shell程式傳遞引數可以擴大程式的用途。以前面提到的ba.sh程式為例:
  $cat >re.sh
  cd $workdir
  cpio -i < /dev/fd0
  ^d

  程式re.sh恢復了ba.sh程式備份的所有檔案。若只從軟盤上恢復一個指定的檔案,可以用該檔名作為引數,傳遞給Shell程式re.sh:

  程式改寫如下:
  $cat >re2.sh
  cd $workdir
  cpio -i $1 < /dev/fd0
  ^d

  使用者可以指定要恢復的檔案,例如fname

  $re2.sh fname

此時檔案fname作為第一個位置引數傳遞給re2.sh,re2.sh的缺點是要恢復兩個或多個檔案要重複執行,我們可以用$*變數傳遞不確定的引數給程式:

  $cat >re3.sh
  cd $workdir
  cpio -i $* < /dev/fd0
  ^d

  我們就可以恢復多個檔案,例如fname1,fname2,fname3
  $re3.sh fname1 fname2 fname3
  (以上程式re.sh,re2.sh,re3.sh,假設使用者已經chmod了可執行權利)

  因為沒有賦值的變數可以作為NULL看待,所以若是程式re3.sh在執行時候沒賦予引數,那麼一個空值將被插入到cpio命令中。該命令將恢復所有儲存的檔案。

條件判斷語句

  條件判斷語句是程式設計語言中十分重要的語句,該語句的含義是當某一條件滿足時,執行指定的一組命令。

1>if - then語句

  格式: if command1
     then
       command2
       command3
     fi      ---(if 語句結束)
       command4

  每個程式或命令執行結束後都有一個返回的狀態,使用者可以用Shell變數$?獲得這一狀態。if語句檢查前面命令執行的返回狀態,若該命令成功執行,那麼在then和fi之間的命令都將被執行。在上面的命令序列中,command1和command4總要執行。若command1成功執行,command2和command3也將執行。

  請看下面程式:
    #unload -program to backup and remove files
    cd $1
    ls -a | cpio -o > /dev/mnt0
    rm *

  該程式在備份資料後,刪除檔案,但當cpio命令不能成功執行時,rm命令還是把資料刪除了,我們可不希望這樣,為了避免此情況,可以用if
- then語句:
    #--解除安裝和判斷刪除程式

    cd $1
    if ls -a | cpio > /dev/mnt0
    then
      rm *
    fi
  上面程式在cpio執行成功後才刪除檔案

同時,若執行沒有成功,我們希望得到提示,sh中的echo命令可以向使用者顯示訊息,並顯示後換行,上面程式可以寫成:
     #--解除安裝和判斷刪除程式
    cd $1
    if ls -a | cpio > /dev/mnt0
    then
      echo "正刪除檔案資料... ..."
      rm *
    fi

  echo命令可以使用一些特殊的逃逸字元進行格式化輸出,下面是這些字元及其含義:

    b  Backspace
    c  顯示後不換行
    f  在終端上螢幕的開始處顯示
    n  換行
    r  回車
    t  製表符
    v  垂直製表符
       反斜框
    nnn 用1,2或3位8進位制整數表示一個ASCII碼字元

2>if - then - else語句

  不用多說它的作用,別的高階語言中都有,格式為:
  if command1
  then
    command2
    command3
  else
    command4
    command5
  fi

  在此結構中,command1中是先執行,當command1成功執行時,將執行command2和command3,否則執行command4和command5

  注意看下面程式:
    #備份程式
    cd $1
    if ls -a |cpio -o > /dev/mnt0
    then
      echo "刪除源資料... ..."
      rm *
    else
      echo "磁帶備份失敗!"
    fi

3>test命令進行條件測試

  if語句可以透過測試命令執行的返回狀態來控制命令的執行,若要測試其他條件,在bsh中可以使用test命令。該命令檢測某一條件,當條件為真時返回0,否則返回非0值。test命令可以使Shell程式中的if語句象其他程式語言中的條件判斷語句一樣,具有很強的功能。

  test命令的使用方法為:
    test condition
  可測試的條件分為4類:
  1)測試兩個字串之間的關係。
  2)測試兩個整數之間關係。
  3)測試檔案是否存在或是否具有某種狀態或屬性。
  4)測試多個條件的與(and)或(or)組合。

1、條件語句>>test語句

1>測試字串間的關係

  bsh把所有的命令列和變數都看作字串。一些命令如expr和test可以把字元當作數字進行操作。

  同樣任何數字也可以作為字串進行操作。

  使用者可以比較兩個字串相等或不等,也可以測試一個串是否賦了值。有關串的運算子如下:
    str1 = str2      當兩個串有相同內容、長度時為真
    str1 != str2      當串str1和str2不等時為真
    -n str1        當串的長度大於0時為真(串非空)
    -z str1        當串的長度為0時為真(空串)
    str1         當串str1為非空時為真

  不但Shell程式可以使用test進行條件判斷,test命令也可以獨立執行,如:

    $str1=abcd
    $test $str1 = abcd
    $echo $?
    結果顯示:0

與上例中第一行賦值語句中的等號不同,test命令中的等號兩邊必須要有空格。本例test命令共有3個引數。注意兩個串相等必須是長度和內容都相等。

    $str1="abcd "
    $test "$str1" = abcd
    $echo $?
    結果顯示:1

  上面str1包含5個字元,其中最後一個為空格符。而test命令中的另一個串只有4個字元,所以兩串不等,test返回1。

  不帶任何運算子和使用-n運算子測試一個串結果是一樣的,例如:

    $str1=abce
    $test $str1
    $echo $?
    結果顯示:0    
    $test -n $str1
    $echo $?
    結果顯示:0

  但是,上面兩條命令也有一點差別,反映出了使用test命令潛在的問題,請看下例:

    $str1="   "
    $test $str1
    $echo $?
    結果顯示:1
    $test -n "$str1"
    $echo $?
    結果顯示:0
    $test -n $str1
    結果顯示:test:argument expected

  上例中,第一次測試為假因為Shell在執行命令列之前首先要進行變數替換,即把$str1換成空格,然後shell又將命令列上的空格刪除,故 test命令測試到的為空串。而在第二次測試中,變數替換後空格位於括號內,故不會被刪除,test測試到的是一個包含空格的串,在第三次測試中,shell把空格刪除,只把-n傳個test命令,所以顯示引數錯。

2>測試兩個整數之間關係

  test命令與expr命令一樣,也可以把字元轉變成整數,然後對其操作。test命令對兩個數進行比較,使用的運算子如下:

    int1 -eq int2    兩數相等為真
    int1 -ne int2    兩數不等為真
    int1 -gt int2    int1大於int2為真
    int1 -ge int2    int1大於等於int2為真
    int1 -lt int2    int1小於int2為真
    int1 -le int2    int1小於等於int2為真

  下面的例子反映了字串比較與數字比較的不同:

    $str1=1234
    $str2=01234
    $test $str1 = $str2
    $echo $?
    結果顯示:1
    $test $str1 -eq $str2
    $echo $?
    結果顯示:0

3>有關檔案的測試

  使用test進行的第三類測試是測試檔案的狀態,使用者可以測試檔案是否存在,是否可寫以及其他檔案屬性。下面是檔案測試時使用的選項。注意只有檔案存在時,才有可能為真。

  -r file     使用者可讀為真
  -w file     使用者可寫為真
  -x file     使用者可執行為真
  -f file     檔案為正規檔案為真
  -d file     檔案為目錄為真
  -c file     檔案為字元特殊檔案為真
  -b file     檔案為塊特殊檔案為真
  -s file     檔案大小非0時為真
  -t file     當檔案描述符(預設為1)指定的裝置為終端時為真
4>複雜的條件測試(and 、or 、not)
  -a         與
  -o        或
  !        非
  就是組合條件了,任何高階語言中都有的(NOT 、AND 、OR),例如:
    $test -r em.null -a -s em.null
    $echo $?
    結果顯示:1
    說明了em.null並不是可讀並且非空的檔案

5>另一種執行test的方法

  bsh中還有另一種執行test命令的方法,就是把測試條件放到一對[
]中,例如:
    $int1=4
    $[ $int1 -gt 2 ]
    $echo $?
    結果顯示:0

要注意在[ 的後面和 ]符號的前面要有一個空格。
  下面我們用test命令寫個簡單但比較完善的程式:

    #-- 備份程式
  
    #-- 檢查引數
    if [ $# -ne 1 ]
    then

      echo "請在程式名後面指出要備份檔案所在目錄!"
      exit 1
    fi
    #-- 檢查目錄名是否有效
    if [ ! -d "$1" ]
    then
      echo "$1 不是一個目錄!"
      exit 2
    fi
    cd $1
    ls -a | cpio -o >/dev/mnt0
    if [ $? -eq 0 ]
    then
      rm *
    else
      echo "cpio執行不成功!備份失敗..."
      exit 3
    fi

6>空命令

  在Bsh中用 : 代表空命令,就是充個數,什麼都不做

7>巢狀if語句和elif結構

  檢查條件1
  A:當條件1為真,則執行一部分操作
  B:若條件1為假,檢查條件2
    1)若條件2為真,執行另外一部分操作
    2)若條件2為假,檢查條件3
    3)若條件3為真,執行其他一部分操作
  語法如下:
    if command
    then
      command
    else
      if command
      then
        command
      else
        if command
        then
          command
        fi
      fi
    fi

8>elif語句

  巢狀if語句有時會給使用者帶來混亂,特別是什麼時候fi語句很難判斷。因此Bourne
Shell又提供了elif語句。elif是else-if的縮寫,它表示是if語句的繼續。格式為:

    if command
    then
      command
    elif command
    then
      command
    elif command
    then
      command
    fi

  上面介紹的巢狀if語句和elif語句完成相同的功能,使用者可以根據自己的喜好選擇一種使用。

9>case語句

  前面說的elif語句替代if-then-else語句,但有時在程式設計 時還會遇到對同一變數進行多次的測試,該情況可以用多個elif語句實現,但還有一種更簡單的方法就是用case語句。

  case語句不但取代了多個elif和then語句,還可以用變數值對多個模式進行匹配,當某個模式與變數值匹配後,其後的一系列命令將被執行,下面是case語句使用的語句。

  case value in
   pattem 1)
    command
    command;;
   pattem 2)
    command
    command;;
   ....
   pattem)
    command;
  esac

  case語句只執行其中的一組命令,當變數值與多個模式相匹配時,只有第一個匹配的模式對應的命令被執行。";;"表示該模式對應的命令部分程式。

  透過學習下面的read語句,我們們再舉例子說明case語句的用法。

10>read語句

  Shell程式不但可以透過命令列引數得到輸入資料,還可以使用read命令提示使用者輸入資料,其語法格式為:

  read var1 var2... ...varn

當Bsh遇到一個read語句時,在標準輸入檔案中讀取資料直到一個換行符。此時Shell在解釋輸入行時,不進行檔名或變數的替換,只是簡單地刪除多餘的空格。然後Shell將輸入行的第一個字的內容給變數1,第二個給變數2,直到所有變數都賦上值或是輸入行為空。若輸入行中字的個數超過變數個數,Shell將把輸入行中剩餘的所有字的內容都賦給最後一個變數。當變數個數多於輸入行字的個數時候,多於的變數將賦一個空值。輸入行的每一個字是由空格分隔的一個字母和數字組成的字串。

  $read var1 var2 var3
    輸入:Hello my friend
  
  $echo $var1 $var2 $var3
    結果顯示:Hello my friend
  $echo $var2
    結果顯示:my

下面用個read和case的例子結束本部分的學習:

  #--互動式備份,恢復程式
  echo "輸入要備份檔案所在目錄:c"
  read WORKDIR
  if [ !-d $WORKDIR ]
  then
    echo "Sorry,$WORKDIR is not a directory"
    exit 1
  fi
  cd $WORKDIR
  echo "輸入選擇:"
  echo _
  echo "1.恢復到 $WORKDIR"
  echo "2.備份 $WORKDIR"
  echo "0.退出"
  echo
  echo "c"
  read CHOICE
  case "$CHOICE" in
   1)echo "恢復中... ..."
    cpio -i < /dev/mnt0;;
   2)echo "備份中... ..."
    ls | cpio -o > /dev/mnt0;;
   0)exit 1
   *)exit 1
  esac
  if [ $? -ne 0 ]
  then
   echo "程式執行中出現錯誤!"
  else
   echo "操作成功!"
  fi  
 
  在上面程式碼中,"*"定義了其他模式下不匹配時的預設操作。

迴圈語句

  前面介紹的程式和所學的語句都是從頭到尾成一條主線下來,或是成分支結構,在日常管理UNIX的過程中,經常要重複的做一些操作,處理批次的問題,這就涉及到了迴圈結構,同高階語言相似,UNIX的Shell也提供了強大的迴圈處理語句。

  Bsh語言中有三種迴圈語句-while迴圈、until迴圈、for迴圈,下面透過具體的例子分別介紹這三種結構。

While迴圈

  在while迴圈語句中,當某一條件為真時,執行指定的命令。語句的結構如下:

while command
do
  command
  command
  … …
done

示例程式碼如下:

#測試while迴圈小程式

x_t=1
  while [ $x_t -lt 5 ]
  do
     mm=` expr $x_t * $int `  #注意""的作用
     echo "$mm"
     x_t=` expr $x_t + 1 `   #注意expr的用法
  done
  echo "THE WHILE IS END!n"

程式的執行結果如下:
1
4
9
16
THE WHILE IS END

  在上述程式中,當變數x_t的值小於5的時候,執行while迴圈中的語句。在第五次迴圈時,
[ $x_t-lt5]命令返回非零值,於是程式執行done後面的程式碼。
現在利用while迴圈,可以改進我們早些時候用的備份資料的例子,當使用者指定的目錄備份完畢後,使用while迴圈使程式執行一次可以備份多個使用者指定的目錄。程式碼如下:

echo "歡迎使用備份小程式"

  ANS=Y
  while [ $ANS = Y -o $ANS = y ]
  do
    echo _
    #讀目錄名
    echo "輸入要備份的目錄名:c"
    read DIR
    if [ ! -d $DIR ]
    then
        echo "$DIR不是一個目錄!"
        exit 1
    fi
    cd $DIR
    echo "請選擇:"
    echo _
    echo "1 恢復資料到 $DIR"
    echo "2 備份$DIR的資料"
    echo
    echo "請選擇:c"
    read CHOICE
    case "$CHOICE" in
       1) echo "恢復中… …"
        cpio -i        2) echo "備份中… …"
        cpio -o >/dev/rmt0;;
       *) echo "選擇無效"
    esac
    if [ $? -ne 0 ]
    then
       echo "cpio執行過程中出現問題"
       exit 2
    fi
    echo "繼續別的目錄嗎?(Y/y)c"
    read ANS
  done

  在程式開始,我們給變數ANS符值為Y,根據whlie的判斷條件,程式進入while迴圈,執行do-done中的語句,每次迴圈都要求使用者輸入 ANS的值用來判斷是否進行下次重複執行do-done中的語句。如果使用者輸入的條件不滿足while語句條件,迴圈結束,程式執行done後面的語句。

Until語句

  While語句中,只要某條件為真,則重複執行迴圈程式碼,until語句正好同while相反,該語句使迴圈程式碼重複執行,直到遇到某一條件為真才停止。

Until語句的結構如下:
until command
  do
    command
    command
    … …
  done

  可以用until語句替換上面備份程式的while語句,完成同樣的功能:

until [ $ANS != Y -a $ANS != y ]

for 迴圈
  在介紹for迴圈之前,我們要學個非常有用的unix命令:shift。我們知道,對於位置變數或命令列引數,其個數必須是確定的,或者當Shell 程式不知道其個數時,可以把所有引數一起賦值給變數$*。若使用者要求Shell在不知道位置變數個數的情況下,還能逐個的把引數一一處理,也就是在$1後為$2,在$2後面為$3等。在
shift命令執行前變數$1的值在shift命令執行後就不可用了。

示例如下:

#測試shift命令(x_shift.sh)
until [ $# -eq 0 ]
do
echo "第一個引數為: $1 引數個數為: $#"
shift
done
執行以上程式x_shift.sh:
$./x_shift.sh 1 2 3 4

結果顯示如下:

第一個引數為: 1 引數個數為: 3
第一個引數為: 2 引數個數為: 2
第一個引數為: 3 引數個數為: 1
第一個引數為: 4 引數個數為: 0

從上可知shift命令每執行一次,變數的個數($#)減一,而變數值提前一位,下面程式碼用until和shift命令計算所有命令列引數的和。

#shift上檔命令的應用(x_shift2.sh)
if [ $# -eq 0 ]
then
echo "Usage:x_shift2.sh 引數"
exit 1
fi
sum=0
until [ $# -eq 0 ]
do
sum=`expr $sum + $1`
shift
done
echo "sum is: $sum"

執行上述程式:

$x_shift2.sh 10 20 15

其顯示結果為:

45

  shift命令還有另外一個重要用途,Bsh定義了9個位置變數,從$1到$9,這並不意味著使用者在命令列只能使用9個引數,藉助shift命令可以訪問多於9個的引數。

  Shift命令一次移動引數的個數由其所帶的引數指定。例如當shell程式處理完前九個命令列引數後,可以使用shift
9命令把$10移到$1。

  在熟悉了shift命令後,我們一起看看,Bsh程式中非常有用的for迴圈語句,這種迴圈同上面說的while和until迴圈不同,for語句中的迴圈是否執行並不由某個條件的真和假來決定,決定for迴圈是否繼續的條件是參數列中是否還有未處理的引數。

For語句的結構如下:

for variable in arg1 arg2 … argn
do
command
command
… …
done

下面是for迴圈的簡單例子:

for LETTER in a b c d
do
echo $LETTER
done

程式執行結果如下:

a
b
c
d

在上面計算引數和的例子中,我們可以用for迴圈,實現如下:

#測試 for 程式(x_for.sh)

if [ $# -eq 0 ]
then
   echo "Usage:x_for.sh 引數… …"
   exit 1
fi
sum=0
for I in $*
do
   sum=`expr $sum + $I`
done
echo "sum is: $sum"

中斷迴圈指令

  在程式迴圈語句中,我們有時候希望遇到某中情況時候結束本次迴圈執行下次迴圈或結束這個迴圈,這就涉及到兩條語句:continue和break。 continue命令可使程式忽略其後迴圈體中的其他指令,直接進行下次迴圈,而break命令則立刻結束迴圈,執行迴圈體後面的的語句。

#測試continue
I=1
while [ $I -lt 10 ]
do
  if [ $I -eq 3 ]
  then
    continue
  fi
  if [ $I -eq 7 ]
  then
    break
  fi
  echo "$Ic"
done

執行上面程式,結果如下:

12456789

與或結構

使用與/或結構有條件的執行命令

  Shell程式中可以使用多種不同的方法完成相同的功能,例如until和while語句就可以完成相同的功能,同樣,除了if-then-else 結構可以使命令有條件的執行外,$$和||運算子也能完成上述功能。在C語言中這兩個運算子分別表示邏輯與和邏輯或操作。在Bourne
Shell中,用&&連線兩條命令的含義只有前面一條命令成功執行了,後面的命令才會執行。

  &&操作的形式為:

    command && command

  例如語句:

    rm $TEMPDIR/* && echo "Files successfully removed"

  只有rm命令成功執行以後,才會執行echo命令。若用if-then語句實現上述功能,形式為:

    if rm $TEMPDIR/*
    then
      echo "Files successfully removed"
    fi
  相反,用||連線兩條命令的含義為只有第一條命令執行失敗才執行第二條命令,例如:

    rm $TEMPDIR/* || echo "File were not removed"

  上面語句的等價形式為:

    if rm $TEMPDIR/*
    then
      :
    else
      echo "Files were not removed"
    fi
  這兩種運算子可以聯合使用,如在下面的命令列中,只有command1和command2執行成功後,command3才會執行:

    command1 && command2 && command3

  下面的命令列表示只有command1成功執行,command2不成功執行時,才會執行command3。

  &&和||運算子可以簡化命令條件執行的格式,但一般只用於一條命令的條件執行。如果許多命令都使用這兩個運算子,那麼整個程式的可讀性將變的很差,所以在多條命令的條件執行時,最好採用可讀性好的if語句。

函式

  現在我們介紹Shell程式中的函式部分,基本上任何高階語言都支援函式這個東西,能讓我們勝好多事情的東西,至少省的頻繁的敲擊相同的東西,好了come
on

Shell程式中的函式

  函式又叫做子程式,可以在程式中的任何地方被呼叫,其格式如下:

  函式名字()
  {
    command
    ... ...
    command;
  }

  Shell程式的任何地方都可以用命令
"函式名字" 呼叫,使用函式的好處有兩點,一點是使用函式可以把一個複雜的程式化為多個模組,易於管理,符合結構化程式的設計思想,另一個好處是程式碼的重用。

  Shell函式和Shel程式比較相似,它們的區別在於Shell程式在子Shell中執行,而Shell函式在當前Shell中執行。因此,在當前Shell中可以看到Shell函式對變數的修改。在任何Shell中都可以定義函式,包括互動式Shell。

  例如:

    $dir() {ls -l;}

    結果是我們在$後面打dir,其顯示結果同ls
-l的作用是相同的。該dir函式將一直保留到使用者從系統退出,或執行了如下所示的unset命令:
    $unset dir
    下面的例子說明了函式還可以接受位置引數:

    $dir(){_
    >echo "permission    ln owner   group    file sz last
access
    >ls -l $*;
    >}

    執行 dir a* 看產生什麼結果

    引數a*傳遞到dir函式中並且代替了$*

    通常Shell程式將在子Shell中執行,該程式對變數的改變只在子Shell中有效而在當前Shell中無效。"."命令可以使Shell程式在當前Shell中執行。使用者可以在當前Shell中定義函式和對變數賦值。通常用下面命令來重新初使化.profile對Shell環境的設定。
    $ . .profile
  由於看到這部分相對簡單,我們還是順便說說trap好了

使用trap命令進行例外處理

  使用者編寫程式在程式執行時可能會發生一些例外情況,比如執行該程式的使用者按中斷鍵或使用kill命令,或者控制終端突然與系統斷開等。unix系統中的上述情況會使系統向程式發一個訊號,通常情況下該訊號使程式終止執行。有時侯使用者希望程式在接到終止訊號時進行一些特殊的操作。若程式在執行時產生一些臨時檔案,又因接受到的訊號而終止。那麼該程式產生的臨時檔案將保留下來。在bsh中,使用者可以使用trap命令修改程式接收到終止訊號時進行的預設操作。
  trap命令格式如下:

     trap command_string signals


多數系統中共有15種發給程式的訊號,預設情況下大多數訊號都會使程式終止。使用者最好查閱自己系統的文擋,看看本系統內使用的訊號種類。除了訊號為9(真正的kill訊號)不能使用trap命令外,其他訊號所帶來的操作都可以用trap命令進行指定。下面是trap命令中經常使用的幾種訊號:

    訊號   功能
    
     1     掛起
     2    操作中斷
     15    軟終止(kill訊號)

  若命令串中包含不只一條命令,必須使用引號將整個命令括起來,具體是單引號還是雙引號,由使用者是否需要變數替換決定。"
"替換,' '不替換。

  使用下面trap命令可以使程式在接收到掛起、中斷或kill訊號時,首先把臨時檔案刪除,然後退出:

    trap "rm $TEMPDIR/* $$;exit" 1 2 15

  在上面例子中,當Shell讀取trap命令時,首先對$TEMPDIR和$$進行變數替換,替換之後的命令串將被儲存在trap表中,若上例中trap命令使用單引號時,trap命令執行時候,不進行變數替換,而把命令串 rm
$TEMPDIR/*
$$;exit 放到trap表中,當檢測到訊號時,程式解釋執行trap表中的命令串,此時進行變數替換。前面變數$TEMPDIR和$$的值為執行trap指令時候的值,後一種情況中變數的值為程式接收到訊號時候的值,所以
"、'一定要區分仔細。

  下面命令的含義為使用者按二次中斷鍵後,程式才終止:

    trap 'trap 2' 2

  一般trap命令中的命令串中幾乎都包含exit語句,上面rm的例子若無exit語句,接收到訊號rm命令執行完後程式將掛起。但有時使用者也需要程式在接到訊號後掛起,例如當終端和系統斷開後,使用者發出掛起訊號,並執行空命令,如下:

    trap : 1

  若使用者想取消前trap指令設定的命令串,可以再執行trap命令,在命令中不指定命令串表示接收到訊號後進行預設的操作,命令如下:
    trap 1

規範Shell

獲取UNIX型別的選項:

  unix有一個優點就是標準UNIX命令在執行時都具有相同的命令列格式:

  command -options parameters

  如果在執行Shell程式也採用上述格式,Bourne
Shell中提供了一條獲取和處理命令列選項的語句,即getopts語句。該語句的格式為:

  getopts option_string variable

  其中option_string中包含一個有效的單字元選項。若getopts命令在命令列中發現了連字元,那麼它將用連字元後面的字元同 option_string相比較。若有匹配,則把變數variable的值設為該選項。若無匹配,則variable設為?。當getopts發現連字元後面沒有字元,會返回一個非零的狀態值。Shell程式中可以利用getopts的返回值建立一個迴圈。

  下面程式碼說明了date命令中怎麼使用getopts命令處理各種選項,該程式除了完成unix的標準命令date的功能外,還增加了許多新的選項。
  #新date程式
  if [ $# -lt 1 ]
  then
    date
  else
    while getopts mdyDHMSTJjwahr OPTION
    do
      case $OPTION
      in
        m)date '+%m';;
        d)date '+%d';;
        y)date '+%y';;
        D)date '+%D';;
        H0date '+%H';;
        M)date '+%M';;
        S)date '+%S';;
        T)date '+%T';;
        j)date '+%j';;
        J)date '+%y%j';;
        w)date '+%w';;
        a)date '+%a';;
        h)date '+%h';;
        r)date '+%r';;
        ?)echo "無效的選項!$OPTION";;
      esac
    done
  fi

有時侯選項中還帶一個值,getopts命令同樣也支援這一功能。這時需要在option_string中選項字母后加一個冒號。當getopts命令發現冒號後,會從命令列該選項後讀取該值。若該值存在,那麼將被存在一個特殊的變數OPTARG中。如果該值不存在,getopts命令將在OPTARG中存放一個問號,並且在標準錯誤輸出上顯示一條訊息。

  下面的例子,實現複製一個檔案,並給檔案賦一個新的名字。-c選項指定程式複製的次數,-v選項要求顯示新建立檔案的檔名。

  #--複製程式

  COPIES=1
  VERBOSE=N
  while getopts vc:OPTION
  do
    case $OPTION
    in
      c)COPIES=$OPTARG;;
      v)VERBOSE=Y;;
      ?)echo "無效引數!"
        exit 1;;
    esac
  done
  if [ $OPTIND -gt $# ]
  then
    echo "No file name specified"
     exit 2
  fi
  shift 'expr $OPTIND - 1'
  FILE=$1
  COPY=0
  while [ $COPIES -gt $COPY ]
  do
    COPY='expr $COPY + 1'
    cp $FILE $ {FILE} $ {COPY}
    if [ VERBOSE = Y }
    then
      echo ${FILE} $ {COPY}
    fi
  done

規範Shell:

  我們知道環境變數PS1是提示符,看下面程式chdir:
  if [ ! -d "$!" ]
  then
    echo "$1 is not a directory"
    exit 1
  fi
  cd $1
  PS1="'pwd'>"
  export PS1

  我們執行:

    $chdir /usr/ice666

  結果提示符號變成/usr/ice666>了嗎?沒有,為什麼?

  原因在於:chdir在子Shell中執行,變數PS1的修改在當前Shell中也不會起作用,若要chdir完成意想中的功能,必須在當前 Shell中執行該命令。最好的方法就是把其改成一個函式並且在.profile檔案中定義。但若要把函式放到單個檔案中並在當前Shell中執行,則需要使用
. 命令,並將chdir重寫成一個函式,把其中的exit改寫成return。下面程式碼是
.ice_ps的內容:

  #--提示符
  chdir()
  {
  if [ !-d "$1" ]
  then
    echo " $1 is not a directory"
    return
  fi
  cd $1
  PS1="'pwd'>"
  export PS1;
  }

  然後我們在.profile檔案中加入下面語句

  .ice_ps

  然後在切換目錄的時候,我們用chdir命令,結果是什麼呢,自己實驗好了!
 
除錯Shell程式

1>除錯shell程式

  使用者剛編寫完Shell程式中,不可避免的會有錯誤,這時我們可以利用Bsh中提供的跟蹤選項,該選項會顯示剛剛執行的命令及引數。使用者可以透過set命令開啟-x選項或在啟動Shell使用-x選項將Shell設定成跟蹤模式。例如有下面程式碼ice_tx:

  if [ $# -eq 0 ]
  then
    echo "usage:sumints integer list"
    exit 1
  fi
  sum=0
  until [ $# -eq 0 ]
  do
    sum='expr $sum + $1'
    shift
  done
  echo $sum

  我們用跟蹤模式執行:

  $sh -x ice_tx 2 3 4
  結果顯示:
  +[ 3 -eq 0 ]
  +sum=0
  +[ 3 -eq 0 ]
  +expr 0+2
  +sum=2
  +shift
  +[ 2 -eq 0 ]
  +expr 2+3
  +sum=5
  +shift
  +[ 1 -eq 0 ]
  +expr 5+4
  +sum=9
  +[ 0 -eq 0 ]
  +echo 9
  9

  從上面可以看出,跟蹤模式下Shell顯示執行的每一條命令以及該命令使用的變數替換後的引數值。一些控制字如if、then、until等沒顯示。

2>命令分組

  Shell中若干命令可以組成一個單元一起執行。為了標識一組命令,這些命令必須放到"()"或"{}"中。放在"()"中的命令將在子Shell中執行,而放在"{}"中的命令將在當前Shell中執行。子Shell中執行的命令不影響當前Shell的變數。當前Shell中執行的命令影響當前 Shell的變數。

  $NUMBER=2
  $(A=2;B=2;NUMBER='expr $A+$B';echo $NUMBER)
  結果為:4
  $echo $NUMBER
  結果為:2
  如果把上面的()變成{},結果會是怎麼樣的呢?

3>使用Shell分層管理器shl

  UNIX是一個多道程式設計的作業系統 ,一些UNIX系統利用這一特性提供了Shell層次管理器shl。使用shl使用者一次可以開啟多個層次的Shell,其中活躍的Shell可以從終端上獲得輸入。但所有Shell的輸出都可在終端上顯示,除非顯示被禁止。

  多個Shell中有一個為shl,當使用者在某個Shell中工作時,可以透過使用特殊字元(一般為Ctrl+z)返回shl。為了同其他Shell區別,shl中提示符為">>>"。當使用者工作在Shell層次管理器中時,可以建立、啟用和刪除Shell,下面是shl中使用的命令。

  create name    產生名為name的層次
  delete name    刪除名為name的層次
  block name     禁止名為name的層次的輸出
  unblock name    恢復名為name的層次的輸出
  resume name    啟用名為name的層次
  toggle       啟用近來經常使用的層次
  name        啟用名為name的層次

  layers [-l] name 
對於表中的每個層次,顯示其正在執行的程式的程式號,-l選項要求顯示詳細資訊。

  help        顯示shl命令的幫助資訊
  quit        退出shl以及所有被啟用的層次

總結

  在前面我們主要介紹了sh的變數、基本語法、程式設計等。如果掌握了這些內容,在學習其他UNIX下程式設計 語言的時候,相信有一定的好處,我們說了,在大多數的UNIX中都提供Bourn
Shell,而且很少有象sh這樣強大的指令碼編輯語言了,是系統管理員和程式設計師的一筆財富,並且不需要額外的軟體環境,對檔案等處理藉助unix命令,實現起來比c實現還要簡單。

http://blog.csdn.net/coofucoo/archive/2009/12/13/4990275.aspx

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/24790158/viewspace-1041131/,如需轉載,請註明出處,否則將追究法律責任。

相關文章