本章讨論bash shell的循環指令for、while和until
13.1 for指令
重複執行一系列指令在程式設計中很常見。
bash shell提供了for指令,允許你建立一個周遊一系列值的循環。每次疊代都使用其中一個值來執行已定義好的一組指令。下面是基本格式
for var in list
do
command
done
在list參數中需要提供疊代中要用到的一系列值。會依次疊代下去。每次疊代中,var會包含清單中要用到的一系列值。
do 和 done直接輸入的指令可以是一條或多條标準的bash shell指令。
13.1.1 讀取清單中的值
每次for指令周遊值清單,它都會将清單中的下一個值賦給$var變量。最後一次疊代後,$var變量的值會在shell腳本中剩餘部分一直保持有效。(除非你修改了它)
13.1.2 讀取清單中的複雜值
清單值的單引号是個大麻煩。
有兩個方法可以解決
1)使用轉義字元\。将單引号轉義
2)使用雙引号來定義用到單引号的值
在某個值兩邊使用雙引号時,shell并不會将雙引号當成值的一部分
13.1.3 從變量讀取清單
将一系列的值都集中存儲在了一個變量中,然後需要周遊變量中的整個清單。
#!/bin/bash
# for test
name1="xcx1 xcx2 xcx3"
name2=$name1" xcx4"
#for name in xc\'y "xcy'1 haha" xcy2 xcy3 xcy4 xcy5
for name in $name2
echo "Hi, My name is $name"
echo "The last people is $name"
name2包含了用于疊代的标準文本值清單。注意。name2用了另一個複制語句向name2變量包含的以有清單中添(或者說拼接)加了一個值。
13.1.4 從指令讀取值
生成清單中所需值的另外一個途徑就是使用指令的輸出。
可以用指令替換來執行任何能産生輸出的指令,然後在for指令中使用該指令的輸出。
例子:
建立一個檔案states,内容如下:

再建一個test2
file="states"
for state in $(cat $file)
echo "Visit beautiful $state"
運作就好了。
for仍然以每次一行的方式周遊的cat指令輸出的結果。
13.1.5 更改字段分隔符
1.特殊環境變量IFS:内部字段分割符。定義了bash shell用作字段分隔符的一系列字元。
2.預設情況下會将下列字元當做字段分隔符。1)空格 2)制表符 3)換行符
3. 如果bash shell 在資料中看到了這些字元中的任意一個,它就會假定這表明了清單中一個新資料字段的開始。
在處理包含空格的資料時會比較麻煩。是以需要修改IFS的值。
隻識别換行符,就需要這麼做:IFS=$’\n’。将這個語句假如腳本中,告訴bash shell在資料值中忽略空格和制表符。
IFS=$’\n’
還有一些絕妙用法:假如需要周遊一個檔案中用冒号分割的值。就可以IFS=:
如果需要指定多個字元,隻需要将它們在指派行中串起來就行。IFS=$’\n’:;” 将換行符、冒号、分号、雙引号作為字段分隔符
13.1.6 用通配符讀取目錄
可以用for指令來自動周遊目錄中的檔案。進行此操作時,必須在檔案名或路徑名中使用通配符。
它會強制使用檔案擴充比對(生成比對指定通配符的檔案名或路徑名的過程)。
比如下面的例子:
1 #!/bin/bash
2 for file in /home/xcy/shell/*
3 do
4 if [ -d "$file" ] # 加雙引号為了解決檔案名含有空格的問題
5 then
6 echo "$file is directory"
7 elif [ -f "$file" ] # 如果檔案名有空格,沒有雙引号就會出錯
8 then
9 echo "$file is file"
10 fi
11 done
for語句首先使用了檔案擴充比對來周遊通配符生成的檔案清單,然後會周遊清單中的下一個檔案。可以将任意多的通配符放進清單中。
13.2 C語言風格的for指令
13.2.1 C語言的for指令
以下是bash中C語言風格的for循環的基本格式:
for (( variable assignment ; condition ; interation process ))
for (( a = 1; a < 10; a++ ))
(1)變量指派可以有空格
(2)條件中的變量不以美元符開頭
(3)疊代過程的算式沒有用expr指令格式。
2 # C for test
3 for (( i = 10; i > 0; i-- ))
4 do
5 echo "For Test: i = $i"
6 done
13.2.2 使用多個變量
C語言風格的for指令允許為疊代使用多個變量。循環會單獨處理每個變量,可以為每個變量定義不同的疊代過程。
盡管可以使用多個變量,但你隻能在for循環中定義一種條件。
2 # C for multiple variables test
3 for (( i=10, b = 0; i > 0; i--, b++ ))
5 echo "For Test: i = $i, b = $b"
13.3 while指令
某種意義是if-then和for循環的混雜體。
while指令允許定義一個要測試的指令,然後循環執行一組指令,隻要定義的測試指令傳回的退出狀态碼0.它會在每次疊代的一開始測試test指令。在test指令傳回非0退出狀态碼時,while會停止執行那組指令。(test傳回0,就接着疊代,否則暫停)
13.3.1 while的基本格式
while test command
other commands
關鍵在于test command的退出狀态碼要随着循環中運作的指令而改變。否則就會停不下來
例子:用方括号檢查循環指令中用的shell的變量的值
2 i=10
3 while [ $i -gt 5 ] # 相當于 >
5 echo "i = $i"
6 i=$[ $i - 1 ] # 不能用i--
7 done
13.3.2 使用多個測試指令
可以在while後面接多個測試指令,隻有最後一個測試指令的退出狀态碼會被用來決定什麼時候結束循環。
2 # multicommand test
3 var=2
4 while echo "var = $var"
5 [ $var -ge 0 ] # -ge 相當于大于等于 >=
6 do
7 echo "this is inside the loop"
8 var=$[ $var - 1 ]
9 done
結果:
說明每次疊代中所有的指令都會執行,包括測試指令失敗的最後一次疊代。
另外,如何指定多個測試指令。每個測試指令都出現再單獨的一行上。
13.4 until指令
和while相反。until指令要求你指定一個通常傳回非0退出狀态碼的測試指令。
隻有測試指令退出狀态碼不為0,bash shell才會執行循環中列出的指令。
一旦傳回了退出狀态碼0,循環就結束了。
格式:
until test commands
other commands
2 # until test
3 var=100
4 until [ $var -lt 0 ] # 滿足條件則結束,不滿足則進循環
5 # -eq ==
6 # -ge >=
7 # -lt <
8 do
9 echo "until test: var = $var"
10 var=$[ $var - 25 ]
也可以執行多個測試指令,隻在最後一個成立時停止。
13.5嵌套循環
循環語句可以在循環内使用任意類型的指令,包括其他循環指令。
注意在循環嵌套時執行次數是兩次循環次數相乘。
2 var1=3
3 for (( var1=3; var1>0; var1-- ))
5 echo "for: var1 = $var1"
6 var2=3
7 while [ $var2 -gt 0 ]
8 do
9 echo " while: var2 = $var2"
10 var2=$[ $var2 - 1 ]
11
12 var3=3
13 until [ $var3 -eq 0 ]
14 do
15 echo " until: var3 = $var3"
16 var3=$[ $var3 - 1 ]
17 done
18 done
19 done
13.6循環處理檔案資料
通常需要周遊存儲在檔案中的資料,需要結合兩種技術:
1)使用嵌套循環
2)修改IFS環境變量
2 # changing the IFS value
3 IFS.OLD=$IFS
4 IFS=$'\n' # 分隔符變為換行符
5 for entry in $(cat /etc/passwd)
7 echo "Values in [$entry]"
8 IFS=: # 分隔符變為冒号
9 for value in $entry
10 do
11 echo " $value"
12 done
13 done
外循環解析一行一行的使用者資訊。内循環通過冒号分割,解析一個使用者的具體資訊。
13.7 控制循環
有兩個指令可以控制循環内部的情況:
1)break 2)continue
13.7.1 break指令
退出循環的一種簡單方法。可以退出任意類型的循環,包括while和until。
下面幾種情況可以使用break指令。
1.跳出單個循環
執行break時,它會嘗試跳出目前正在執行的循環。
2 for var in 10 9 8 7 6 5 4 3 2 1
4 if [ $var -eq 5 ]
6 echo "this is exec break;"
7 break
8 fi
9 echo "var = $var"
10 done
這個方法也适用于while和until循環。
2.跳出内部循環
處理多個循環時,break會自動終止你所在的最内層的循環。
内層循環終止了,外層循環依然會繼續執行。
3.跳出外部循環
有時你在内部循環,但需要停止外部循環。break指令接受單個指令行參數。
break n
n指定了要跳出的循環層級。預設情況下n為1.表示跳出目前循環。
若為2,就表示跳出上一級的外部循環。
1 #!/bin/bash
2 for(( i=5; i>0; i-- ))
4 echo "outer loop: i = $i"
5 for(( j = 0; j < 100; j++ ))
6 do
7 echo "inside loop: j = $j"
8 if [ $j -eq 5 ]
9 then
10 break 2 # 跳出上一級循環
11 # break # 跳出目前循環
12 fi
13 done
14 done
13.7.2 continue指令
提前終止某次循環中的指令,不會完全終止整個循環。
2 for(( i=0; i < 10; i++ ))
4 if [ $i -gt 4 ] && [ $i -lt 8 ]
6 continue
7 fi
8 echo "haha i = $i"
注意:這個會跳過剩餘的指令,如果在剩餘的指令中要對測試條件變量進行改變就會出問題。這裡需要留個心眼。
也可以通過指令行參數指定要繼續執行哪一級循環。 continue n
2 for(( i=0; i < 5; i++ ))
4 echo "out loop; i = $i"
5 for(( j=0; j<4; j++ ))
7 echo " inside loop +++ j = $j"
8 if [ $j -eq 2 ]
10 continue 2 #繼續上一級循環 還可以不接2,表示繼續目前循環
11 fi
12 echo " inside loop --- j = $j"
注意break和continue的差別:
break用于完全結束一個循環,後面的循環也不執行了。
continue用來結束目前循環,後面的循環還會執行。
比如:
for(i = 0; i < 10; i++)
do
if [ $i –eq 5]
then
break # 6 , 7 , 8 ,9 就都不會列印了,結束了。
# continue # 僅僅不列印5
fi
echo “i = $i”
13.8 處理循環的輸出
直接上例子吧。直接在done後面接 > xxx.txt
13.9 執行個體
13.9.1 查找可執行檔案
找出系統中有哪些可執行檔案可供使用,隻找PATH環境變量中所有的目錄就行了
2 # find files in the PATH
3 IFS=:
4 for folder in $PATH # 将各個目錄放入folder中
5 do
6 echo "$folder"
7 for file in $folder/* # 疊代指定目錄中的所有檔案
9 if [ -x $file ] # 檢查是否有可執行權限
10 then
11 echo " $file"
13.9.2 建立多個使用者賬戶
讓系統管理者更輕松。用腳本建立使用者
1.先建立一個文本,裡面放使用者id和name。用逗号分隔
2. 再去讀取上述檔案中的資訊
while IFS=',' read -r userid name
這個還是蠻有技巧的。read會自動讀取讀取.csv文本檔案的下一行内容,不需要再寫一個循環來處理。
read傳回false時(就是讀取完了)while就會退出,妙哉。
代碼如下:
2 # shell add user account
3 input="users.csv"
4 while IFS=',' read -r userid name # 讀取裡面的資料,IFS要設為逗号
6 echo "adding id:$userid name:$name"
7 useradd -c "$name" -m $userid
8 done < "$input"
執行需要sudo權限。
13.9.2 再删除建立的使用者
2 # xcy test, del user
3 IFS=$'\n'
4 for user in $(cat /etc/passwd)
6 # echo "$user"
7 IFS=:
8 for value in $user
9 do
10 if [[ $value == xiaochongyong* ]] # 這個*有點通配符的意思
11 then
12 echo "Userid: $value"
13 userdel $value
14 fi
15 break
16 done
17 done
注意那個break,因為/etc/passwd第一條就是userid,這裡讀取完userid就退出目前循環。