shell循環和分支

循環和分支

對代碼塊的操作是構造組織shell腳本的關鍵. 循環和分支結構爲腳本編程提供了操作代碼塊的工具.

10.1. Loops

循環就是重複一些命令的代碼塊,如果條件不滿足就退出循環.

for loops

for arg in [list]

這是一個基本的循環結構.它與Cfor結構有很大不同.

forarg in [list]
do
   command(s)...
done

http://manual.51yip.com/shell/common/note.png

在循環的每次執行中,arg將順序的存取list中列出的變量..

   1 for arg  in "$var1" "$var2" "$var3" ...  "$varN" 

   2 # 在第1次循環中, arg =  $var1      

   3 # 在第2次循環中, arg =  $var2      

   4 # 在第3次循環中, arg =  $var3      

   5 # ...

   6 # 在第N次循環中, arg =  $varN

   7                                                                         

   8 # 在[list]中的參數加上雙引號是爲了防止單詞被不合理地分割.

list中的參數允許包含通配符.

如果dofor想在同一行出現,那麼在它們之間需要添加一個";".

forarg in [list] ; do



例子 10-1. 循環的一個簡單例子

    1 #!/bin/bash

   2 # 列出所有的行星名稱.

   3 

   4 for  planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto

   5 do

   6   echo $planet  # 每個行星被單獨打印在一行上.

   7 done

   8 

   9 echo

  10 

  11 for  planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune  Pluto"

  12 # 所有的行星名稱打印在同一行上.

  13 # 整個'list'只有一個變量,用""封成一個變量.

  14 do

  15   echo $planet

  16 done

  17                                                                                           

  18 exit 0



http://manual.51yip.com/shell/common/note.png

每個[list]中的元素都可能包含多個參數.在處理參數組時,這是非常有用的.在這種情況下,使用set命令(例子  11-15)來強制解析每個[list]中的元素,並且分配每個解析出來的部分到一個位置參數中.

                                                                                                                                                   



例子 10-2. 每個[list]元素帶兩個參數的for循環

    1 #!/bin/bash

   2 # 再訪行星.

   3 

   4 # 分配行星的名字和它距太陽的距離.

   5 

   6 for  planet in "Mercury 36" "Venus 67" "Earth  93"  "Mars 142"  "Jupiter 483"

   7 do

   8   set -- $planet  # Parses variable "planet" and  sets positional parameters.

   9   # "--" 將防止$planet爲空,或者是以一個破折號開頭.

  10 

  11   # 可能需要保存原始的位置參數,因爲它們被覆蓋了.

  12   # 一種方法就是使用數組,

  13   #         original_params=("$@")

  14 

  15   echo "$1                                                                                               $2,000,000 miles from the sun"

  16   #-------two   tabs---把後邊的0和$2連接起來

  17 done

  18 

  19 #  (Thanks, S.C., for additional clarification.)

  20 

  21 exit 0



可以在for循環中的[list]位置放入一個變量.



例子 10-3. 文件信息: 對包含在變量中的文件列表進行操作

    1 #!/bin/bash

   2 #  fileinfo.sh

   3 

    4 FILES="/usr/sbin/accept

    5 /usr/sbin/pwck

    6 /usr/sbin/chroot

    7 /usr/bin/fakefile

    8 /sbin/badblocks

    9 /sbin/ypbind"     # 你關心的文件列表.

  10                   # 扔進去一個假文件, /usr/bin/fakefile.

  11 

  12 echo

  13 

  14 for  file in $FILES

  15 do

  16 

  17   if [ ! -e "$file" ]       # 檢查文件是否存在.

  18   then

  19     echo "$file does not exist.";  echo

  20                                                                                     continue                # 繼續下一個.

  21    fi

  22 

  23   ls -l $file | awk '{ print $9 "         file size: " $5 }'  # 打印2個域.

  24   whatis `basename $file`   # 文件信息.

  25   # 注意whatis數據庫需要提前建立好.

  26   # 要想達到這個目的, 以root身份運行/usr/bin/makewhatis.

  27   echo

   28 done 

  29 

  30 exit 0



如果在for循環的[list]中有通配符(*?),那將會產生文件名擴展,也就是文件名擴展(globbing.



例子 10-4. for循環中操作文件

    1 #!/bin/bash

   2 #  list-                                                                                  

glob.sh:  在for循環中使用文件名擴展產生 [list]

   3 

   4 echo

   5 

   6 for  file in *

   7 #           ^   在表達式中識別文件擴展符時,

   8 #+             Bash 將執行文件名擴展.

   9 do

  10   ls -l "$file"  # Lists all files in $PWD (current  directory).

  11   #  回想一下,通配符"*"能夠匹配所有文件,

  12   #+ 然而,在"文件擴展符"中,是不能匹配"."文件的.

  13 

  14   #  如果沒匹配到任何文件,那它將擴展成自己

  15   #  爲了不讓這種情況發生,那就設置nullglob選項

  16   #+    (shopt -s nullglob).

  17   #   Thanks, S.C.

  18 done

  19 

  20 echo;  echo

  21 

  22 for  file in [jx]*

  23 do

  24   rm -f $file    # 只刪除當前目錄下以"j"或"x"開頭的文件.

  25   echo "Removed file  \"$file\"".

  26 done

  27 

  28 echo

  29 

  30 exit 0



在一個for循環中忽略in [list]部分的話,將會使循環操作$@(從命令行傳遞給腳本的參數列表).一個非常好的例子,例子A-16.



例子 10-5. for循環中省略in [list]

                                                                                       1 #!/bin/bash

   2 

   3 #  使用兩種方法來調用這個腳本,一種是帶參數的情況,另一種不帶參數.

   4 #+ 觀察此腳本的行爲各是什麼樣的?

   5 

   6 for a

   7 do

   8  echo  -n "$a "

   9 done

  10 

  11 #  沒有[list],所以循環將操作'$@'

  12 #+ (包括空白的命令參數列表).

  13 

  14 echo

  15 

  16 exit 0



也可以使用命令替換(command substitution來產生for循環的[list].具體見例子12-49, 例子10-10 例子12-43.



例子10-6. 使用命令替換來產生for循環的[list]

                                                                                      1 #!/bin/bash

   2 #  for-loopcmd.sh: 帶[list]的for循環

   3 #+  [list]是由命令替換產生的.

   4 

    5 NUMBERS="9 7 3 8 37.53"

   6 

   7 for  number in `echo $NUMBERS`  # for number  in 9 7 3 8 37.53

   8 do

   9   echo -n "$number "

  10 done

  11 

  12 echo

  13 exit 0



下邊是一個用命令替換來產生[list]的更復雜的例子.



例子 10-7. 對於二進制文件的grep替換

                   

    1 #!/bin/bash                                                                               

   2 #  bin-grep.sh: 在一個二進制文件中定位匹配字串.

   3 

   4 # 對於二進制文件的一個grep替換

   5 # 與"grep -a"的效果相似

   6 

    7 E_BADARGS=65

    8 E_NOFILE=66

   9 

  10 if [ $#  -ne 2 ]

  11 then

  12   echo "Usage: `basename $0`  search_string filename"

  13   exit $E_BADARGS

  14 fi

  15 

  16 if [ !  -f "$2" ]

  17 then

  18   echo "File \"$2\" does not  exist."

  19   exit $E_NOFILE

  20 fi 

  21 

  22 

   23 IFS="\n"          # 由Paulo Marcel Coelho  Aragao提出的建議.

  24 for  word in $( strings "$2" | grep "$1" )

  25 #  "strings" 命令列出二進制文件中的所有字符串.

  26 # 輸出到管道交給"grep",然後由grep命令來過濾字符串.

  27 do

  28   echo $word

  29 done

  30 

  31 # S.C. 指出, 行23 - 29 可以被下邊的這行來代替,

  32 #    strings "$2" | grep  "$1" | tr -s "$IFS" '[\n*]'

  33 

  34 

  35 # 試試用"./bin-grep.sh mem  /bin/ls"來運行這個腳本.

  36 

  37 exit 0



大部分相同.



例子 10-8. 列出系統上的所有用戶

    1 #!/bin/bash

   2 #  userlist.sh

   3 

    4 PASSWORD_FILE=/etc/passwd

    5 n=1           # User  number

   6 

   7 for  name in $(awk 'BEGIN{FS=":"}{print $1}' <  "$PASSWORD_FILE" )

   8 # 域分隔    = :           ^^^^^^

   9 # 打印出第一個域                 ^^^^^^^^

  10 # 從password文件中取得輸入                     ^^^^^^^^^^^^^^^^^

  11 do

  12   echo "USER #$n = $name"

  13   let "n += 1"

   14 done 

  15 

  16 

  17 # USER  #1 = root

  18 # USER  #2 = bin

  19 # USER  #3 = daemon

  20 # ...

  21 # USER  #30 = bozo

  22                                                                                  

  23 exit 0

  24 

  25 #  練習:

  26 #  --------

  27 #  一個普通用戶(或者是一個普通用戶運行的腳本)

  28 #+ 怎麼能讀取/etc/password呢?

  29 #  這是否是一個安全漏洞? 爲什麼是?爲什麼不是?



關於用命令替換來產生[list]的最後的例子.



例子 10-9. 在目錄的所有文件中查找源字串

    1 #!/bin/bash

   2 #  findstring.sh:

   3 # 在一個指定目錄的所有文件中查找一個特定的字符串.

   4 

    5 directory=/usr/bin/

   6 fstring="Free  Software Foundation"  # 查看那個文件中包含FSF.

   7 

   8 for  file in $( find $directory -type f -name '*' | sort )

   9 do

  10   strings -f $file | grep  "$fstring" | sed -e "s%$directory%%"

  11   #  在"sed"表達式中,

  12   #+ 我們必須替換掉正常的替換分隔符"/",

  13   #+ 因爲"/"碰巧是我們需要過濾的字串之一.

  14   #  如果不用"%"代替"/"作爲分隔符,那麼這個操作將失敗,並給出一個錯誤消息.(試試)

   15 done 

  16                                                                                        

  17 exit 0

  18 

  19 #  練習 (容易):

  20 #  ---------------

  21 #  將內部用的$directory和$fstring變量,用從

  22 #+ 命令行參數代替.



for循環的輸出也可以通過管道傳遞到一個或多個命令中.



例子 10-10. 列出目錄中所有的符號連接(symboliclinks)

    1 #!/bin/bash

   2 #  symlinks.sh: 列出目錄中所有的符號連接文件.

   3 

   4 

    5 directory=${1-`pwd`}

   6 #  如果沒有其他的特殊指定,

   7 #+ 默認爲當前工作目錄.

   8 #  下邊的代碼塊,和上邊這句等價.

   9 #  ----------------------------------------------------------

  10 #  ARGS=1                 # 需要一個命令行參數.

  11 #

  12 # if [  $# -ne "$ARGS" ]  # 如果不是一個參數的話...

  13 # then

  14 #   directory=`pwd`      # 當前工作目錄

  15 # else

  16 #   directory=$1

  17 # fi

  18 #  ----------------------------------------------------------

  19 

  20 echo  "symbolic links in directory \"$directory\""

  21 

  22 for  file in "$( find $directory -type l )"   # -type l 就是符號連接文件

  23 do

  24   echo "$file"

  25 done |  sort                                  # 否則列出的文件將是未排序的

  26 #  嚴格上說,此處並不一定非要一個循環不可,

  27 #+ 因爲"find"命令的結果將被擴展成一個單詞.

  28 #  然而,這種方式很容易理解和說明.

  29                                                                                            

  30 #  Dominik 'Aeneas' Schnitzer 指出,

  31 #+ 如果沒將 $( find $directory -type l )用""引用起來的話

  32 #+ 那麼將會把一個帶有空白部分的文件名拆成以空白分隔的兩部分(文件名中允許有空白).

  33 #  即使這隻將取出每個參數的第一個域.

  34 

  35 exit 0

  36 

  37 

  38 # Jean  Helou 建議使用下邊的方法:

  39 

  40 echo  "symbolic links in directory \"$directory\""

  41 # 當前IFS的備份.要小心使用這個值.

   42 OLDIFS=$IFS

  43 IFS=:

  44 

  45 for  file in $(find $directory -type l -printf "%p$IFS")

  46 do     #                               ^^^^^^^^^^^^^^^^

  47        echo "$file"

  48        done|sort



循環的輸出可以重定向到文件中,我們對上邊的例子做了一點修改.



例子 10-11. 將目錄中的符號連接文件名保存到一個文件中

    1 #!/bin/bash

   2 #  symlinks.sh: 列出目錄中所有的符號連接文件.

   3 

    4 OUTFILE=symlinks.list                         # 保存的文件

   5 

    6 directory=${1-`pwd`}

   7 #  如果沒有其他的特殊指定,

   8 #+ 默認爲當前工作目錄.

   9 

  10 

  11 echo  "symbolic links in directory \"$directory\"" >  "$OUTFILE"

  12 echo  "---------------------------" >> "$OUTFILE"

  13                                                                                            

  14 for  file in "$( find $directory -type l )"    # -type l 爲尋找類型爲符號鏈接的文件

  15 do

  16   echo "$file"

  17 done |  sort >> "$OUTFILE"                     # 循環的輸出

  18 #           ^^^^^^^^^^^^^                       重定向到一個文件中.

  19 

  20 exit 0



有一種非常像C語言的for循環的語法形式.這需要使用(()).



例子 10-12. 一個C風格的for循環

    1 #!/bin/bash

   2 # 兩種循環到10的方法.

   3 

   4 echo

   5 

   6 # 標準語法.

   7 for a  in 1 2 3 4 5 6 7 8 9 10

   8 do

   9   echo -n "$a "

   10 done 

  11 

  12 echo;  echo

  13 

  14 #  +==========================================+

  15 

  16 # 現在, 讓我們用C風格的語法做同樣的事.

  17 

   18 LIMIT=10

  19 

  20 for  ((a=1; a <= LIMIT ; a++))  # 雙圓括號, 並且"LIMIT"變量前邊沒有 "$".

  21 do

  22   echo -n "$a "

   23 done                            # 這是一個借用'ksh93'的結構.

  24 

  25 echo;  echo

  26 

  27 #  +=========================================================================+

  28 

  29 # 讓我們使用C的逗號操作符,來同時增加兩個變量的值.

  30 

  31 for  ((a=1, b=1; a <= LIMIT ; a++, b++))   # 逗號將同時進行2條操作.

  32 do

  33   echo -n "$a-$b "

  34 done

  35 

  36 echo;  echo

  37 

  38 exit 0



參考例子26-15, 例子26-16, 例子A-6.

---

現在來一個現實生活中使用的for循環.



例子 10-13. batch mode中使用efax

    1 #!/bin/bash

   2 #  Faxing ('fax' 必須已經被安裝過了).

   3 

   4 EXPECTED_ARGS=2

    5 E_BADARGS=65

   6 

   7 if [ $#  -ne $EXPECTED_ARGS ]

   8 # 檢查命令行參數的個數是否正確.

   9 then

  10    echo "Usage: `basename $0` phone#  text-file"

  11    exit $E_BADARGS

  12 fi

  13 

  14 

  15 if [ !  -f "$2" ]

  16 then

  17   echo "File $2 is not a text file"

  18   exit $E_BADARGS

  19 fi

  20  

  21 

  22 fax  make $2              # 從文本文件中創建傳真格式的文件.

  23 

  24 for  file in $(ls $2.0*)  # 連接轉換過的文件.

  25                          # 在變量列表中使用通配符.

  26 do

  27   fil="$fil $file"

   28 done 

  29 

  30 efax -d  /dev/ttyS3 -o1 -t "T$1" $fil    # 幹活的地方.

  31 

  32 

  33 # S.C. 指出, 通過下邊的命令可以省去for循環.

  34 #    efax -d /dev/ttyS3 -o1 -t "T$1"  $2.0*

  35 # 但這並不十分有講解意義[嘿嘿].

  36                                                                                             

  37 exit 0



while

這種結構在循環的開頭判斷條件是否滿足,如果條件一直滿足,那就一直循環下去(0退出碼[exitstatus]).for 循環的區別是,這種結構適合用在循環次數未知的情況下.

while [condition]
do
  command...
done

for循環一樣,如果想把do和條件放到同一行上還是需要一個";".

while [condition] ; do

注意一下某種特定的while循環,比如getopts結構,好像和這裏所介紹的模版有點脫節.



例子 10-14. 簡單的while循環

    1 #!/bin/bash

   2 

   3 var0=0

    4 LIMIT=10

   5 

   6 while [  "$var0" -lt "$LIMIT" ]

   7 do

   8   echo -n "$var0 "        # -n 將會阻止產生新行.

   9   #             ^           空格,數字之間的分隔.

  10 

  11   var0=`expr $var0 + 1`   # var0=$(($var0+1))  也可以.

  12                           # var0=$((var0 +  1)) 也可以.

  13                           # let "var0 +=  1"    也可以.

                                                                                     14 done                       # 使用其他的方法也行.

  15 

  16 echo

  17 

  18 exit 0



例子 10-15. 另一個while循環

    1 #!/bin/bash

   2 

   3 echo

                                                                                           4                                # 等價於:

   5 while [  "$var1" != "end" ]      # while test "$var1" != "end"

   6 do

   7   echo "Input variable #1 (end to exit)  "

   8   read var1                    # 爲什麼不使用'read $var1'?

   9   echo "variable #1 = $var1"   # 因爲包含"#"字符,所以需要"" . . .

  10   # 如果輸入爲'end',那麼就在這裏打印.

  11   # 不在這裏判斷結束,在循環頂判斷.

  12   echo

   13 done 

  14 

  15 exit 0



一個while循環可以有多個判斷條件,但是隻有最後一個才能決定是否退出循環.然而這需要一種有點不同的循環語法.



例子 10-16. 多條件的while循環

    1 #!/bin/bash

   2 

    3 var1=unset

    4 previous=$var1

   5 

   6 while  echo "previous-variable = $previous"

   7       echo

   8       previous=$var1

   9       [ "$var1" != end ] # 記錄之前的$var1.

  10       # 這個"while"循環中有4個條件, 但是隻有最後一個能控制循環.

  11       # 退出狀態由第4個條件決定.

  12 do

  13 echo  "Input variable #1 (end to exit) "

  14   read var1

  15   echo "variable #1 = $var1"

   16 done 

  17 

  18 # 嘗試理解這個腳本的運行過程.

  19 # 這裏還是有點小技巧的.

  20                                                                               

  21 exit 0



for循環一樣,while循環也可通過(())來使用C風格語法.(例子9-30).



例子 10-17. C風格的while循環

    1 #!/bin/bash

   2 #  wh-loopc.sh: 循環10次的while循環.

   3 

    4 LIMIT=10

   5 a=1

   6 

   7 while [  "$a" -le $LIMIT ]

   8 do

   9   echo -n "$a "

  10   let "a+=1"

   11 done           # 到目前爲止都沒什麼令人驚奇的地方.

  12 

  13 echo;  echo

  14 

  15 #  +=================================================================+

  16 

  17 # 現在, 重複C風格的語法.

  18 

  19 ((a =  1))      # a=1

  20 # 雙圓括號允許賦值兩邊的空格,就像C語言一樣.

  21 

  22 while  (( a <= LIMIT ))   # 雙圓括號, 變量前邊沒有"$".

  23 do

  24   echo -n "$a "

  25   ((a += 1))    # let "a+=1"

  26   # Yes, 看到了吧.

  27   # 雙圓括號允許像C風格的語法一樣增加變量的值.

  28 done

  29 

  30 echo

  31 

  32 # 現在,C程序員可以在Bash中找到回家的感覺了吧.

  33 

  34 exit 0



http://manual.51yip.com/shell/common/note.png

while                                                                                                     循環的stdin可以用<重定向到文件.

whild循環的stdin支持管道.

until

這個結構在循環的頂部判斷條件,並且如果條件一直爲false那就一直循環下去.(while相反).

until [condition-is-true]
do
  command...
done

注意: until循環的判斷在循環的頂部,這與某些編程語言是不同的.

for循環一樣,如果想把do和條件放在一行裏,就使用";".

until [condition-is-true] ; do



例子 10-18. until循環

    1 #!/bin/bash

   2 

                                                                                    3 END_CONDITION=end

   4 

   5 until [  "$var1" = "$END_CONDITION" ]

   6 # 在循環的頂部判斷條件.

   7 do

   8   echo "Input variable #1 "

   9   echo "($END_CONDITION to exit)"

  10   read var1

  11   echo "variable #1 = $var1"

  12   echo

   13 done 

  14 

  15 exit 0


轉自 shell在線中文手冊

http://manual.51yip.com/shell/

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章