##shell十三問之13: for what? while與until差在哪?

終於,來到了shell十三問的最後一問了... 長長吐一口氣~~~~

最後要介紹的是shell script設計中常見的循環(loop). 所謂的loop就是script中的一段在一定條件下反覆執行的代碼。

bash shell中常用的loop有如下三種:

  • for
  • while
  • until

###1. for loop

for loop 是從一個清單列表中讀進變量的值, 並依次的循環執行dodone之間的命令行。 例:

for var in one two three four five
do
    echo -----------------
	echo '$var is '$var
	echo
done

上例的執行結果將會是:

  1. for會定義一個叫var的變量,其值依次是one two three four five。
  1. 因為有5個變量值,因此,dodone之間的命令行會被循環執行5次。
  1. 每次循環均用echo產生3個句子。而第二行中不在hard quote之內的$var會被替換。
  1. 當最後一個變量值處理完畢,循環結束。

我們不難看出,在for loop中,變量值的多寡,決定循環的次數。 然而,變量在循環中是否使用則不一定,得視設計需求而定。 倘若for loop沒有使用in這個keyword來制變量清單的話,其值將從 $@(或$*)中繼承:

for var; do
	......
done

Tips:

若你忘記了`positional parameter, 請溫習第9章...

for loop用於處理“清單”(list)項目非常方便, 其清單除了明確指定或從postional parameter取得之外, 也可以從變量替換或者命令替換取得... (再一次提醒:別忘了命令行的“重組”特性) 然而,對於一些“累計變化”的項目(整數的加減),for也能處理:

for ((i = 1; i <= 10; i++))
do
	echo "num is $i"
done

###2. while loop

除了for loop, 上面的例子, 我們也可改用while loop來做到:

num=1
while [ "$num" -le 10 ]; do
	echo "num is $num"
	num=$(($num + 1))
done

while loop的原理與for loop稍有不同: 它不是逐次處理清單中的變量值, 而是取決於while 後面的命令行的return value:

  • 若為true, 則執行dodone之間的命令, 然後重新判斷while後的return value。
  • 若為false,則不再執行dodone之間的命令而結束循環。

分析上例:

  1. while之前,定義變量num=1.
  1. 然後測試(test)$num是否小於或等於10.
  1. 結果為true,於是執行echo並將num的值加1.
  1. 再作第二輪測試,此時num的值為1+1=2,依然小於或等於10,因此,為true,循環繼續。
  1. 直到num為10+1=11時,測試才會失敗...於是結束循環。

我們不難發現: while的測試結果永遠為true的話,那循環將一直永久執行下去

while:; do
	echo looping...
done

上面的**:是bash的null command,不做任何動作, 除了返回true的return value**。 因此這個循環不會結束,稱作死循環。

死循環的產生有可能是故意設計的(如跑daemon), 也可能是設計的錯誤。

若要結束死循環,可通過signal來終止(如按下ctrl-c). (關於process與signal,等日後有機會再補充,十三問略過。)

####3.until loop

一旦你能夠理解while loop的話,那就能理解until loop: **與while相反, until是在return value 為false時進入循環,否則,結束。 因此,前面的例子,我們也可以輕鬆的用until來寫:

num=1
until [ ! "$num" -le 10 ]; do
	echo "num is $num"
	num=$(($num + 1))
done

或者:

num=1

until [ "$num" -gt 10 ]; do
	echo "num is $num"
	num=$(($num + 1))
done

okay, 關於bash的三個常用的loop暫時介紹到這裡。

###4. shell loop中的break與continue

在結束本章之前,再跟大家補充兩個loop有關的命令:

  • break
  • continue 這兩個命令常用在複合式循環裡, 也就是do ... done之間又有更進一層的loop, 當然,用在單一循環中也未嘗不可啦... _

break用來中斷循環,也就是強迫結束循環。 若break後面指定一個數值n的話,則從裡向外中斷第n個循環, 預設值為 break 1,也就是中斷當前循環。 在使用break時,需要注意的是,它與returnexit是不同的:

  • break是結束loop;
  • return是結束function;
  • exit是結束script/shell;

continue則與break相反:強迫進入下一次循環動作.

若你理解不來的話,那你可簡單的看成: 在continuedone之間的句子略過而返回到循環的頂端...

break相同的是:continue後面也可以指定一個數值n, 以決定繼續哪一層(從裡往外計算)的循環, 預設值為 continue 1,也就是繼續當前的循環。

在shell script設計中,若能善用loop, 將能大幅度提高script在複雜條件下的處理能力。 請多加練習吧...


shell是十三問的總結語


好了,該是到了結束的時候了。 婆婆媽媽地跟大家囉嗦了一堆shell的基礎概念。

目的不是要告訴大家“答案”,而是要帶給大家“啟發”...

在日後的關於shell的討論中,我或許經常用"連接"的方式 指引十三問中的內容。

以便我們在進行技術探討時,彼此能有一些討論的基礎, 而不至於各說各話、徒費時力。

但更希望十三問能帶給你更多的思考與樂趣, 至為重要的是通過實踐來加深理解。

是的,我很重視實踐獨立思考這兩項學習要素。

若你能夠掌握其中的真諦,那請容我說聲: 恭喜十三問你沒白看了 _

p.s. 至於補充問題部分,我暫時不寫了。 而是希望:

  1. 大家補充題目。
  2. 一起來寫心得。

Good luck and happy studing!


##shell十三問原作者**網中人**簽名中的bash的fork bomb

最後,Markdown整理者補上本書的原作者網中人的個性簽名:

** 君子博學而日叄省乎己,則知明而行無過矣。**

一個能讓系統shell崩潰的shell 片段:

:() { :|:& }; :      # <--- 這個別亂跑!好奇會死人的!
echo '十人|日一|十十o' | sed 's/.../&\n/g'   # <--- 跟你講就不聽,再跑這個就好了...

原來是一個bash的fork炸彈:ref:http://en.wikipedia.org/wiki/Fork_bomb

整理後的代碼:

:() {

	:|:&
}
:

代碼分析:

(即除最後一行外)

定義了一個 shell 函數,函數名是:

而這個函數體執行一個後臺命令:|:

即冒號命令(或函數,下文會解釋)的輸出 通過管道再傳給冒號命令做輸入

最後一行執行“:”命令

在各種shell中運行結果分析:

這個代碼只有在 bash 中執行才會出現不斷創建進程而耗盡系統資源的嚴重後果;

在 ksh (Korn shell), sh (Bourne shell)中並不會出現,

在 ksh88 和傳統 unix Bourne shell 中冒號不能做函數名,

即便是在 unix-center freebsd 系統中的 sh 和 pdksh(ksh93 手邊沒有,沒試)中冒號可以做函數名,但還是不會出現那個效果。

原因是 sh、ksh 中內置命令的優先級高於函數,所以執行“:”, 總是執行內置命令“:”而不是剛才定義的那個恐怖函數。

但是在 bash 中就不一樣,bash 中函數的優先級高於內置命令, 所以執行“:”結果會導致不斷的遞歸,而其中有管道操作, 這就需要創建兩個子進程來實現,這樣就會不斷的創建進程而導致資源耗盡。

眾所周知,bash是一款極其強大的shell,提供了強大的交互與編程功能。

這樣的一款shell中自然不會缺少“函數”這個元素來幫助程序進行模塊化的高效開發與管理。 於是產生了由於其特殊的特性,bash擁有了fork炸彈。

Jaromil在2002年設計了最為精簡的一個fork炸彈的實現。

所謂fork炸彈是一種惡意程序,它的內部是一個不斷在fork進程的無限循環.

fork炸彈並不需要有特別的權限即可對系統造成破壞。

fork炸彈實質是一個簡單的遞歸程序。

由於程序是遞歸的,如果沒有任何限制,

這會導致這個簡單的程序迅速耗盡系統裡面的所有資源.


书籍推荐