循環和分支
對代碼塊的操作是構造組織shell腳本的關鍵. 循環和分支結構為腳本編程提供了操作代碼塊的工具.
10.1. Loops
循環就是重復一些命令的代碼塊,如果條件不滿足就退出循環.
for loops
for arg in [list]
這是一個基本的循環結構.它與C的for結構有很大不同.
forarg in [list]
do
???command(s)...
done
在循環的每次執行中,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中的參數允許包含通配符.
如果do和for想在同一行出現,那么在它們之間需要添加一個";".
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 |
每個[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 |
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/ |
?
轉載于:https://blog.51cto.com/1362336072/1883295