Unix系列shell程式編寫:下篇

helloxchen發表於2010-10-21
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

程式執行結果如下:

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實現還要簡單。
[@more@]

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

相關文章