什么是遞歸函數?
一句話,調用自己的函數稱為遞歸函數!
#!/bin/bash
declare -i count
checkoutCount(){
read -p "Enter an count: " count
if [ $count -eq 100 ]; then
echo "Count is 100."
else
if [ $count -gt 100 ]; then
echo "count is greater than 100."
else
echo "count is less than 100."
fi
fi
checkoutCount
}
checkoutCount
結果展示:
經典的遞歸函數----江湖俗稱"fork 炸彈"
可能很多人都曾經聽說過 fork 炸彈,它實際上只是一個非常簡單的遞歸程序,程序所做的事情只有一樣:不斷 fork 一個新進程。由於程序是遞歸的,如果沒有任何限制,這會導致這個簡單的程序迅速耗盡系統里面的所有資源。
清單1. bash 中的 fork 炸彈
.(){ .|.& };.
清單2. bash 中的 fork 炸彈的解釋
1 .()
2 {
3 .|.&
4 }
5 ;
6 .
第 1 行說明下面要定義一個函數,函數名為小數點,沒有可選參數。
第 2 行表示函數體開始。
第 3 行是函數體真正要做的事情,首先它遞歸調用本函數,然后利用管道調用一個新進程(它要做的事情也是遞歸調用本函數),並將其放到后台執行。
第 4 行表示函數體結束。
第 5 行並不會執行什么操作,在命令行中用來分隔兩個命令用。從總體來看,它表明這段程序包含兩個部分,首先定義了一個函數,然后調用這個函數。
第 6 行表示調用本函數。
對 於函數名,大家可能會有所疑惑,小數點也能做函數名使用嗎?畢竟小數點是 shell 的一個內嵌命令,用來在當前 shell 環境中讀取指定文件,並運行其中的命令。實際上的確可以,這取決於 bash 對命令的解釋順序。默認情況下,bash 處於非 POSIX 模式,此時對命令的解釋順序如下:
關鍵字,例如 if、for 等。
別名。別名不能與關鍵字相同,但是可以為關鍵字定義別名,例如 end=fi。
特殊內嵌命令,例如 break、continue 等。POSIX 定義的特殊內嵌命令包括:.(小數點)、:(冒號)、break、continue、eval、exec、exit、export、readonly、 return、set、shift、times、trap 和 unset。bash 又增加了一個特殊的內嵌命令 source。
函數。如果處於非 POSIX 模式,bash 會優先匹配函數,然后再匹配內嵌命令。
非特殊內嵌命令,例如 cd、test 等。
腳本和可執行程序。在 PATH 環境變量指定的目錄中進行搜索,返回第一個匹配項。
由 於默認情況下,bash 處於非 POSIX 模式,因此 fork 炸彈中的小數點會優先當成一個函數進行匹配。(實際上,Jaromil 最初的設計並沒有使用小數點,而是使用的冒號,也能起到完全相同的效果。)要使用 POSIX 模式來運行 bash 腳本,可以使用以下三種方法:
使用 --posix 選項啟動 bash。
在運行 bash 之后,執行 set -o posix 命令。
使用 /bin/sh 。
最 后一種方法比較有趣,盡管 sh 在大部分系統上是一個指向 bash 的符號鏈接,但是它所啟用的卻是 POSIX 模式,所有的行為都完全遵守 POSIX 規范。在清單 3 給出的例子中,我們可以發現,小數點在默認 bash 中被解釋成一個函數,能夠正常執行;但是在 sh 中,小數點卻被當作一個內嵌命令,因此調用函數時會被認為存在語法錯誤,無法正常執行。
清單3. bash 與 sh 對命令匹配順序的區別
[root@localhost ~]# ls -l /bin/bash /bin/sh
-rwxr-xr-x 1 root root 735144 2007-08-31 22:20 /bin/bash
lrwxrwxrwx 1 root root 4 2007-12-18 13:26 /bin/sh -> bash
[root@localhost ~]# echo $SHELL
/bin/bash
[root@localhost ~]# .() { echo hello; } ; .
hello
[root@localhost ~]# sh
sh-3.2# echo $SHELL
/bin/bash
sh-3.2# .() { echo hello; } ; .
sh: `.': not a valid identifier
sh: .: filename argument required
.: usage: . filename [arguments]
sh-3.2#
一旦運行清單 1 給出的 fork 炸彈,會以2的指數次冪的速度不斷產生新進程,這會導致系統資源會被迅速耗光,最終除非重新啟動機器,否則基本上就毫無辦法了。為了防止這會造成太大的損 害,我們可以使用 ulimit 限制每個用戶能夠創建的進程數,如清單 4 所示。
清單4. 限制用戶可以創建的進程數
[root@localhost ~]# ulimit -u 128
[root@localhost ~]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
max nice (-e) 20
file size (blocks, -f) unlimited
pending signals (-i) unlimited
max locked memory (kbytes, -l) unlimited
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) unlimited
max rt priority (-r) unlimited
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 128
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
[root@localhost ~]# .() { .|.& } ; .
[1] 6152
[root@localhost ~]# bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
bash: fork: Resource temporarily unavailable
在清單 4 中,我們將用戶可以創建的最大進程數限制為 128,執行 fork 炸彈會迅速 fork 出大量進程,此后會由於資源不足而無法繼續執行。