unix 下shell編程(三)

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 

程序執行結果如下: 






在上面計算參數和的例子中,我們可以用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 "$I\c" 
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實現還要簡單。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章