循環(huán)
循環(huán)是當(dāng)循環(huán)控制條件為真時,一系列命令迭代執(zhí)行的代碼塊
for 循環(huán)
for arg in [list]
這是 shell 中最基本的循環(huán)結(jié)構(gòu)奋岁,它與C語言形式的循環(huán)有著明顯的不同
for arg in [list]
do
command(s)...
done
在循環(huán)的過程中,arg 會從 list 中連續(xù)獲得每一個變量的值
for arg in "$var1" "$var2" "$var3" ... "$varN"
# 第一次循環(huán)中缀辩,arg = $var1
# 第二次循環(huán)中势告,arg = $var2
# 第三次循環(huán)中没佑,arg = $var3
# ...
# 第 N 次循環(huán)中鸵赖,arg = $varN
# 為了防止可能的字符分割問題务漩,[list] 中的參數(shù)都需要被引用。
參數(shù) list 中允許含有通配符
如果 do
和 for
寫在同一行時卫漫,需要在 list 之后加上一個分號
for arg in [list] ; do
樣例-1. 簡單的 for 循環(huán)
#!/bin/bash
# 列出太陽系的所有行星。
for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto
do
echo $planet # 每一行輸出一個行星肾砂。
done
echo; echo
for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto"
# 所有的行星都輸出在一行上列赎。
# 整個 'list' 被包裹在引號中時是作為一個單一的變量。
# 為什么?因?yàn)榭崭褚彩亲兞康囊徊糠帧?do
echo $planet
done
echo; echo "Whoops! Pluto is no longer a planet!"
exit 0
list
中的每一個元素中都可能含有多個參數(shù)包吝。這在處理參數(shù)組中非常有用饼煞。在這種情況下,使用set
命令強(qiáng)制解析 list
中的每一個元素诗越,并將元素的每一個部分分配給位置參數(shù)
樣例-2. for
循環(huán) [list] 中的每一個變量有兩個參數(shù)的情況
#!/bin/bash
# 讓行星再躺次槍砖瞧。
# 將每個行星與其到太陽的距離放在一起。
for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483"
do
set -- $planet # 解析變量 "planet"
#+ 并將其每個部分賦值給位置參數(shù)嚷狞。
# "--" 防止一些極端情況块促,比如 $planet 為空或者以破折號開頭。
# 因?yàn)槲恢脜?shù)會被覆蓋掉床未,因此需要先保存原先的位置參數(shù)竭翠。
# 你可以使用數(shù)組來保存
# original_params=("$@")
echo "$1 $2,000,000 miles from the sum"
#-------兩個制表符---將后面的一系列 0 連到參數(shù) $2 上。
done
exit 0
一個單一變量也可以成為 for 循環(huán)中的 list
樣例-3. 文件信息:查看一個單一變量中含有的文件列表的文件信息
#!/bin/bash
# fileinfo.sh
FILES="/usr/sbin/accept
/usr/sbin/pwck
/usr/sbin/chroot
/usr/bin/fakefile
/sbin/badblocks
/sbin/ypbind" # 你可能會感興趣的一系列文件薇搁。
# 包含一個不存在的文件斋扰,/usr/bin/fakefile。
echo
for file in $FILES
do
if [ ! -e "$file" ] # 檢查文件是否存在啃洋。
then
echo "$file does not exist."; echo
continue # 繼續(xù)判斷下一個文件传货。
fi
ls -l $file | awk '{ print $8 " file size: " $5 }' # 輸出其中的兩個域。
whatis `basename $file` # 文件信息宏娄。
# 腳本正常運(yùn)行需要注意提前設(shè)置好 whatis 的數(shù)據(jù)问裕。
# 使用 root 權(quán)限運(yùn)行 /usr/bin/makewhatis 可以完成。
echo
done
exit 0
for 循環(huán)中的 list
可以是一個參數(shù)
樣例-4. 操作含有一系列文件的參數(shù)
#!/bin/bash
filename="*txt"
for file in $filename
do
echo "Contents of $file"
echo "---"
cat "$file"
echo
done
如果在匹配文件擴(kuò)展名的 for 循環(huán)中的 [list] 含有通配符(* 和 ?)绝编,那么將會進(jìn)行文件名擴(kuò)展
樣例-5. 在 for 循環(huán)中操作文件
#!/bin/bash
# list-glob.sh: 通過文件名擴(kuò)展在 for 循環(huán)中產(chǎn)生 [list]僻澎。
# 通配 = 文件名擴(kuò)展。
echo
for file in *
# ^ Bash 在檢測到通配表達(dá)式時十饥,
#+ 會進(jìn)行文件名擴(kuò)展窟勃。
do
ls -l "$file" # 列出 $PWD(當(dāng)前工作目錄)下的所有文件。
# 回憶一下逗堵,通配符 "*" 會匹配所有的文件名秉氧,
#+ 但是,在文件名擴(kuò)展中蜒秤,他將不會匹配以點(diǎn)開頭的文件汁咏。
# 如果沒有匹配到文件,那么它將會擴(kuò)展為它自身作媚。
# 為了防止出現(xiàn)這種情況攘滩,需要設(shè)置 nullglob 選項(xiàng)。
#+ (shopt -s nullglob)
done
echo; echo
for file in [jx]*
do
rm -f $file # 刪除當(dāng)前目錄下所有以 "j" 或 "x" 開頭的文件纸泡。
echo "Removed file \"$file\"".
done
echo
exit 0
如果在 for
循環(huán)中省略 in [list]
部分漂问,那么循環(huán)將會遍歷位置參數(shù)($@
樣例-6. 缺少 in [list]
的 for
循環(huán)
#!/bin/bash
# 嘗試在帶參數(shù)和不帶參數(shù)兩種情況下調(diào)用這個腳本,觀察發(fā)生了什么。
for a
do
echo -n "$a "
done
# 缺失 'in list' 的情況下蚤假,循環(huán)會遍歷 '$@'
#+(命令行參數(shù)列表栏饮,包括空格)。
echo
exit 0
可以在 for
循環(huán)中使用 命令代換 生成 list
樣例-7. 在 for
循環(huán)中使用命令代換生成 list
#!/bin/bash
# for-loopcmd.sh: 帶命令代換所生成 [list] 的 for 循環(huán)
NUMBERS="9 7 3 8 37.53"
for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53
do
echo -n "$number "
done
echo
exit 0
下面是使用命令代換生成 list
的更加復(fù)雜的例子
樣例-8. 一種替代 grep 搜索二進(jìn)制文件的方法
#!/bin/bash
# bin-grep.sh: 在二進(jìn)制文件中定位匹配的字符串磷仰。
# 一種替代 `grep` 搜索二進(jìn)制文件的方法
# 與 "grep -a" 的效果類似
E_BADARGS=65
E_NOFILE=66
if [ $# -ne 2 ]
then
echo "Usage: `basename $0` search_string filename"
exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
echo "File \"$2\" does not exist."
exit $E_NOFILE
fi
IFS=$'\012' # 按照 Anton Filippov 的意見應(yīng)該是
# IFS="\n"
for word in $( strings "$2" | grep "$1" )
# "strings" 命令列出二進(jìn)制文件中的所有字符串袍嬉。
# 將結(jié)果通過管道輸出到 "grep" 中,檢查是不是匹配的字符串灶平。
do
echo $word
done
# 可以換成下面的形式:
# strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]'
# 嘗試運(yùn)行腳本 "./bin-grep.sh mem /bin/ls"
exit 0
下面的例子同樣展示了如何使用命令代換生成 list
樣例-9. 列出系統(tǒng)中的所有用戶
#!/bin/bash
# userlist.sh
PASSWORD_FILE=/etc/passwd
n=1 # 用戶數(shù)量
for name in $(awk 'BEGIN{fs=":"}{print $1}' < "$PASSWORD_FILE" )
# 分隔符 = : ^^^^^^
# 輸出第一個域 ^^^^^^^^
# 讀取密碼文件 /etc/passwd ^^^^^^^^^^^^^^^^^
do
echo "USER #$n = $name"
let "n += 1"
done
# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #33 = bozo
exit $?
# 討論:
# -----
# 一個普通用戶是如何讀取 /etc/passwd 文件的伺通?
# 提示:檢查 /etc/passwd 的文件權(quán)限。
# 這算不算是一個安全漏洞民逼?為什么泵殴?
另外一個關(guān)于 [list] 的例子也來自于命令代換
樣例-10. 檢查目錄中所有二進(jìn)制文件
#!/bin/bash
# findstring.sh
# 在指定目錄的二進(jìn)制文件中尋找指定的字符串。
directory=/usr/bin
fstring="Free Software Foundation" # 查看哪些文件來自于 FSF拼苍。
for file in $( find $directory -type f -name '*' | sort )
do
strings -f $file | grep "$fstring" | sed -e "s%$driectory%%"
# 在 "sed" 表達(dá)式中笑诅,你需要替換掉 "/" 分隔符,
#+ 因?yàn)?"/" 是一個會被過濾的字符疮鲫。
# 如果不做替換吆你,將會產(chǎn)生一個錯誤。(你可以嘗試一下俊犯。)
done
exit $?
# 簡單的練習(xí):
# ----------
# 修改腳本妇多,使其可以從命令行參數(shù)中獲取 $directory 和 $fstring。
最后一個關(guān)于 list
和命令代換的例子燕侠,但這個例子中的命令是一個函數(shù)
generate_list ()
{
echo "one two three"
}
for word in $(generate_list) # "word" 獲得函數(shù)執(zhí)行的結(jié)果者祖。
do
echo "$word"
done
# one
# two
# three
for 循環(huán)的結(jié)果可以通過管道導(dǎo)向至一個或多個命令中
樣例-11. 列出目錄中的所有符號鏈接
#!/bin/bash
# symlinks.sh: 列出目錄中的所有符號鏈接。
directory=${1-`pwd`}
# 如果沒有特別指定绢彤,缺省目錄為當(dāng)前工作目錄七问。
# 等價于下面的代碼塊。
# ---------------------------------------------------
# ARGS=1 # 只有一個命令行參數(shù)茫舶。
#
# if [ $# -ne "$ARGS" ] # 如果不是只有一個參數(shù)的情況下
# then
# directory=`pwd` # 設(shè)為當(dāng)前工作目錄械巡。
# else
# directory=$1
# fi
# ---------------------------------------------------
echo "symbolic links in directory \"$directory\""
for file in "$( find $directory -type 1 )" # -type 1 = 符號鏈接
do
echo "$file"
done | sort # 否則文件順序會是亂序。
# 嚴(yán)格的來說這里并不需要使用循環(huán)饶氏,
#+ 因?yàn)?"find" 命令的輸出結(jié)果已經(jīng)被擴(kuò)展成一個單一字符串了讥耗。
# 然而,為了方便大家理解疹启,我們使用了循環(huán)的方式古程。
# Dominik 'Aeneas' Schnitzer 指出,
#+ 不引用 $( find $directory -type 1 ) 的話喊崖,
# 腳本將在文件名包含空格時阻塞挣磨。
exit 0
# --------------------------------------------------------
# Jean Helou 提供了另外一種方法:
echo "symbolic links in directory \"$directory\""
# 備份當(dāng)前的內(nèi)部字段分隔符菲宴。謹(jǐn)慎永遠(yuǎn)沒有壞處。
OLDIFS=$IFS
IFS=:
for file in $(find $directory -type 1 -printf "%p$IFS")
do # ^^^^^^^^^^^^^^^^
echo "$file"
done|sort
# James "Mike" Conley 建議將 Helou 的代碼修改為:
OLDIFS=$IFS
IFS='' # 空的內(nèi)部字段分隔符意味著將不會分隔任何字符串
for file in $( find $directory -type 1 )
do
echo $file
done | sort
# 上面的代碼可以在目錄名包含冒號(前一個允許包含空格)
#+ 的情況下仍舊正常工作
只需要對上一個樣例做一些小小的改動趋急,就可以把在標(biāo)準(zhǔn)輸出 stdout
中的循環(huán) 重定向
到文件中
樣例-12. 將目錄中的所有符號鏈接保存到文件中
#!/bin/bash
# symlinks.sh: 列出目錄中的所有符號鏈接。
OUTFILE=symlinks.list
directory=${1-`pwd`}
# 如果沒有特別指定势誊,缺省目錄為當(dāng)前工作目錄呜达。
echo "symbolic links in directory \"$directory\"" > "$OUTFILE"
echo "---------------------------" >> "$OUTFILE"
for file in "$( find $directory -type 1 )" # -type 1 = 符號鏈接
do
echo "$file"
done | sort >> "$OUTFILE" # 將 stdout 的循環(huán)結(jié)果
# ^^^^^^^^^^^^^ 重定向到文件。
# echo "Output file = $OUTFILE"
exit $?
還有另外一種看起來非常像C語言中循環(huán)那樣的語法粟耻,你需要使用到 雙圓括號
語法
樣例-13. C語言風(fēng)格的循環(huán)
#!/bin/bash
# 用多種方式數(shù)到10查近。
echo
# 基礎(chǔ)版
for a in 1 2 3 4 5 6 7 8 9 10
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 使用 "seq"
for a in `seq 10`
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 使用大括號擴(kuò)展語法
# Bash 3+ 版本有效。
for a in {1..10}
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 現(xiàn)在用類似C語言的語法再實(shí)現(xiàn)一次挤忙。
LIMIT=10
for ((a=1; a <= LIMIT ; a++)) # 雙圓括號語法霜威,不帶 $ 的 LIMIT
do
echo -n "$a "
done
echo; echo
# +==========================================+
# 我們現(xiàn)在使用C語言中的逗號運(yùn)算符來使得兩個變量同時增加。
for ((a=1, b=1; a <= LIMIT ; a++, b++))
do # 逗號連接操作册烈。
echo -n "$a-$b "
done
echo; echo
exit 0
接下來戈泼,我們將展示在真實(shí)環(huán)境中應(yīng)用的循環(huán)
樣例-14. 在批處理模式下使用 efax
#!/bin/bash
# 傳真(必須提前安裝了 'efax' 模塊)酝蜒。
EXPECTED_ARGS=2
E_BADARGS=85
MODEM_PORT="/dev/ttyS2" # 你的電腦可能會不一樣盖矫。
# ^^^^^ PCMCIA 調(diào)制解調(diào)卡缺省端口。
if [ $# -ne $EXPECTED_ARGS ]
# 檢查是不是傳入了適當(dāng)數(shù)量的命令行參數(shù)诅需。
then
echo "Usage: `basename $0` phone# text-file"
exit $E_BADARGS
fi
if [ ! -f "$2" ]
then
echo "File $2 is not a text file."
# File 不是一個正常文件或者文件不存在淀零。
exit $E_BADARGS
fi
fax make $2 # 根據(jù)文本文件創(chuàng)建傳真格式文件挽绩。
for file in $(ls $2.0*) # 連接轉(zhuǎn)換后的文件。
# 在參數(shù)列表中使用通配符(文件名通配)驾中。
do
fil="$fil $file"
done
efax -d "$MODEM_PORT" -t "T$1" $fil # 最后使用 efax唉堪。
# 如果上面一行執(zhí)行失敗,嘗試添加 -o1肩民。
# 上面只能指出 for 循環(huán)可以被壓縮為
# efax -d /dev/ttyS2 -o1 -t "T$1" $2.0*
#+ 但是這并不是一個好主意唠亚。
exit $? # efax 同時也會將診斷信息傳遞給標(biāo)準(zhǔn)輸出。
關(guān)鍵字do
和 done
圈定了 for 循環(huán)代碼塊的范圍此改,但是在一些特殊的情況下趾撵,也可以被大括號取代
for((n=1; n<=10; n++))
# 沒有 do!
{
echo -n "* $n *"
}
# 沒有 done共啃!
# 輸出:
# * 1 ** 2 ** 3 ** 4 ** 5 ** 6 ** 7 ** 8 ** 9 ** 10 *
# 并且 echo $? 返回 0占调,因此 Bash 并不認(rèn)為這是一個錯誤。
echo
# 但是注意在典型的 for 循環(huán) for n in [list] ... 中移剪,
#+ 需要在結(jié)尾加一個分號究珊。
for n in 1 2 3
{ echo -n "$n "; }
# ^
while 循環(huán)
while
循環(huán)結(jié)構(gòu)會在循環(huán)頂部檢測循環(huán)條件,若循環(huán)條件為真 退出狀態(tài) 為0纵苛,則循環(huán)持續(xù)進(jìn)行剿涮。與 for
不同的是言津,while
循環(huán)是在不知道循環(huán)次數(shù)的情況下使用的
while [ condition ]
do
command(s)...
done
在 while
循環(huán)結(jié)構(gòu)中,你不僅可以使用像 if/test
中那樣的 括號結(jié)構(gòu)取试,也可以使用用途更廣泛的 雙括號結(jié)構(gòu)while [[ condition ]]
就像在 for
循環(huán)中那樣悬槽,將 do
和循環(huán)條件放在同一行時需要加一個分號。
while [ condition ] ; do
在 while
循環(huán)中瞬浓,括號結(jié)構(gòu)并不是必須存在的初婆,比如說 getopts
結(jié)構(gòu)
樣例-15. 簡單的 while
循環(huán)
#!/bin/bash
var0=0
LIMIT=10
while [ "$var0" -lt "$LIMIT" ]
# ^ ^
# 必須有空格,因?yàn)檫@是測試結(jié)構(gòu)
do
echo -n "$var0 " # -n 不會另起一行
# ^ 空格用來分開輸出的數(shù)字猿棉。
var0=`expr $var0 + 1` # var0=$(($var0+1)) 效果相同磅叛。
# var0=$((var0 + 1)) 效果相同。
# let "var0 += 1" 效果相同萨赁。
done # 還有許多其他的方法也可以達(dá)到相同的效果弊琴。
echo
exit 0
樣例-16. 另一個例子
#!/bin/bash
echo
# 等價于:
while [ "$var1" != "end" ] # while test "$var1" != "end"
do
echo "Input variable #1 (end to exit) "
read var1 # 不是 'read $var1' (為什么?)杖爽。
echo "variable #1 = $var1" # 因?yàn)榇嬖?"#"敲董,所以需要使用引號。
# 如果輸入的是 "end"慰安,也將會在這里輸出臣缀。
# 在結(jié)束本輪循環(huán)之前都不會再測試循環(huán)條件了。
echo
done
exit 0
一個 while 循環(huán)可以有多個測試條件泻帮,但只有最后的那一個條件決定了循環(huán)是否終止精置。這是一種你需要注意到的不同于其他循環(huán)的語法。
樣例-17. 多條件 while 循環(huán)
#!/bin/bash
var1=unset
previous=$var1
while echo "previous-variable = $previous"
echo
previous=$var1
[ "$var1" != end ] # 記錄下 $var1 之前的值锣杂。
# 在 while 循環(huán)中有4個條件脂倦,但只有最后的那個控制循環(huán)。
# 最后一個條件的退出狀態(tài)才會被記錄元莫。
do
echo "Input variable #1 (end to exit) "
read var1
echo "variable #1 = $var1"
done
# 猜猜這是怎樣實(shí)現(xiàn)的赖阻。
# 這是一個很小的技巧。
exit 0
就像 for
循環(huán)一樣踱蠢, while
循環(huán)也可以使用雙圓括號結(jié)構(gòu)寫得像?C語言那樣
樣例-18. C語言風(fēng)格的 while
循環(huán)
#!/bin/bash
# wh-loopc.sh: 在 "while" 循環(huán)中計數(shù)到10火欧。
LIMIT=10 # 循環(huán)10次。
a=1
while [ "$a" -le $LIMIT ]
do
echo -n "$a "
let "a+=1"
done
echo; echo
# +==============================================+
# 現(xiàn)在我們用C語言風(fēng)格再寫一次茎截。
((a = 1)) # a=1
# 雙圓括號結(jié)構(gòu)允許像C語言一樣在賦值語句中使用空格苇侵。
while (( a <= LIMIT )) # 雙圓括號結(jié)構(gòu),
do #+ 并且沒有使用 "$"企锌。
echo -n "$a "
((a += 1)) # let "a+=1"
# 是的榆浓,就是這樣。
# 雙圓括號結(jié)構(gòu)允許像C語言一樣自增一個變量撕攒。
done
echo
exit 0
在測試部分陡鹃,while
循環(huán)可以調(diào)用 函數(shù)
t=0
condition ()
{
((t++))
if [ $t -lt 5 ]
then
return 0 # true 真
else
return 1 # false 假
fi
}
while condition
# ^^^^^^^^^
# 調(diào)用函數(shù)循環(huán)四次烘浦。
do
echo "Still going: t = $t"
done
# Still going: t = 1
# Still going: t = 2
# Still going: t = 3
# Still going: t = 4
和 if 測試
結(jié)構(gòu)一樣,while
循環(huán)也可以省略括號
while condition
do
command(s) ...
done
在 while
循環(huán)中結(jié)合 read
命令萍鲸,我們就得到了一個非常易于使用的 while read
結(jié)構(gòu)闷叉,它可以用來讀取和解析文件
cat $filename | # 從文件獲得輸入。
while read line # 只要還有可以讀入的行脊阴,循環(huán)就繼續(xù)片习。
do
...
done
# ==================== 摘自樣例腳本 "sd.sh" =================== #
while read value # 一次讀入一個數(shù)據(jù)。
do
rt=$(echo "scale=$SC; $rt + $value" | bc)
(( ct++ ))
done
am=$(echo "scale=$SC; $rt / $ct" | bc)
echo $am; return $ct # 這個功能“返回”了2個值蹬叭。
# 注意:這個技巧在 $ct > 255 的情況下會失效。
# 如果要操作更大的數(shù)字状知,注釋掉上面的 "return $ct" 就可以了秽五。
} <"$datafile" # 傳入數(shù)據(jù)文件。
在 while
循環(huán)后面可以通過 < 將標(biāo)準(zhǔn)輸入 重定位到文件
中饥悴,while
循環(huán)同樣可以 通過管道
傳入標(biāo)準(zhǔn)輸入中
until
與 while
循環(huán)相反坦喘,until
循環(huán)測試其頂部的循環(huán)條件,直到其中的條件為真時停止
until [ condition-is-true ]
do
commands(s)...
done
注意到西设,跟其他的一些編程語言不同瓣铣,until 循環(huán)的測試條件在循環(huán)頂部
就像在 for
循環(huán)中那樣,將 do
和循環(huán)條件放在同一行時需要加一個分號
until[ condition-is-true ] ; do
樣例-19. until 循環(huán)
#!/bin/bash
END_CONDITION=end
until [ "$var1" = "$END_CONDITION" ]
# 在循環(huán)頂部測試條件贷揽。
do
echo "Input variable #1 "
echo "($END_CONDITION to exit)"
read var1
echo "variable #1 = $var1"
echo
done
# --- #
# 就像 "for" 和 "while" 循環(huán)一樣棠笑,
#+ "until" 循環(huán)也可以寫的像C語言一樣。
LIMIT=10
var=0
until (( var > LIMIT ))
do # ^^ ^ ^ ^^ 沒有方括號禽绪,沒有 $ 前綴蓖救。
echo -n "$var "
(( var++ ))
done # 0 1 2 3 4 5 6 7 8 9 10
exit 0
如何在 for
,while
和 until
之間做出選擇印屁?我們知道在C語言中循捺,在已知循環(huán)次數(shù)的情況下更加傾向于使用 for
循環(huán)。但是在Bash
中情況可能更加復(fù)雜一些雄人。Bash
中的 for
循環(huán)相比起其他語言來說从橘,結(jié)構(gòu)更加松散,使用更加靈活础钠。因此使用你認(rèn)為最簡單的就好恰力。
嵌套循環(huán)
嵌套循環(huán),顧名思義就是在循環(huán)里面還有循環(huán)旗吁。外層循環(huán)會不斷的觸發(fā)內(nèi)層循環(huán)直到外層循環(huán)結(jié)束牺勾。當(dāng)然,你仍然可以使用 break
可以終止外層或內(nèi)層的循環(huán)阵漏。
樣例-20. 嵌套循環(huán)
#!/bin/bash
# nested-loop.sh: 嵌套 "for" 循環(huán)驻民。
outer=1 # 設(shè)置外層循環(huán)計數(shù)器翻具。
# 外層循環(huán)。
for a in 1 2 3 4 5
do
echo "Pass $outer in outer loop."
echo "---------------------"
inner=1 # 重設(shè)內(nèi)層循環(huán)計數(shù)器回还。
# =====================================
# 內(nèi)層循環(huán)裆泳。
for b in 1 2 3 4 5
do
echo "Pass $inner in inner loop."
let "inner+=1" # 增加內(nèi)層循環(huán)計數(shù)器。
done
# 內(nèi)層循環(huán)結(jié)束柠硕。
# =====================================
let "outer+=1" # 增加外層循環(huán)計數(shù)器工禾。
echo # 在每次外層循環(huán)輸出中加入空行。
done
# 外層循環(huán)結(jié)束蝗柔。
exit 0
循環(huán)控制
break, continue
break 和 continue 命令的作用和在其他編程語言中的作用一樣闻葵。break
用來中止(跳出)循環(huán),而 continue
則是略過未執(zhí)行的循環(huán)部分癣丧,直接進(jìn)行下一次循環(huán)槽畔。
樣例-21. 循環(huán)中 break
與 continue
的作用
#!/bin/bash
LIMIT=19 # 循環(huán)上界
echo
echo "Printing Numbers 1 through 20 (but not 3 and 11)."
a=0
while [ $a -le "$LIMIT" ]
do
a=$(($a+1))
if [ "$a" -eq 3 ] || [ "$a" -eq 11 ] # 除了 3 和 11。
then
continue # 略過本次循環(huán)的剩余部分胁编。
fi
echo -n "$a " # 當(dāng) a 等于 3 和 11 時厢钧,將不會執(zhí)行這條語句。
done
# 思考:
# 為什么循環(huán)不會輸出到20嬉橙?
echo; echo
echo Printing Numbers 1 through 20, but something happens after 2.
##################################################################
# 用 'break' 代替了 'continue'早直。
a=0
while [ "$a" -le "$LIMIT" ]
do
a=$(($a+1))
if [ "$a" -gt 2 ]
then
break # 中止循環(huán)。
fi
echo -n "$a"
done
echo; echo; echo
exit 0
break
命令接受一個參數(shù)市框,普通的 break
命令僅僅跳出其所在的那層循環(huán)霞扬,而 break N
命令則可以跳出其上 N 層的循環(huán)
樣例-22. 跳出多層循環(huán)
#!/bin/bash
# break-levels.sh: 跳出循環(huán).
# "break N" 跳出 N 層循環(huán)。
for outerloop in 1 2 3 4 5
do
echo -n "Group $outerloop: "
# ------------------------------------------
for innerloop in 1 2 3 4 5
do
echo -n "$innerloop "
if [ "$innerloop" -eq 3 ]
then
break # 嘗試一下 break 2 看看會發(fā)生什么枫振。
# (它同時中止了內(nèi)層和外層循環(huán)祥得。)
fi
done
# ------------------------------------------
echo
done
echo
exit 0
與 break
類似,continue
也接受一個參數(shù)蒋得。普通的 continue
命令僅僅影響其所在的那層循環(huán)级及,而 continue N
命令則可以影響其上 N 層的循環(huán)
樣例-23. continue
影響外層循環(huán)
#!/bin/bash
# "continue N" 命令可以影響其上 N 層循環(huán)。
for outer in I II III IV V # 外層循環(huán)
do
echo; echo -n "Group $outer: "
# --------------------------------------------------------------------
for inner in 1 2 3 4 5 6 7 8 9 10 # 內(nèi)層循環(huán)
do
if [[ "$inner" -eq 7 && "$outer" = "III" ]]
then
continue 2 # 影響兩層循環(huán)额衙,包括“外層循環(huán)”饮焦。
# 將其替換為普通的 "continue",那么只會影響內(nèi)層循環(huán)窍侧。
fi
echo -n "$inner " # 7 8 9 10 將不會出現(xiàn)在 "Group III."中县踢。
done
# --------------------------------------------------------------------
done
echo; echo
# 思考:
# 想一個 "continue N" 在腳本中的實(shí)際應(yīng)用情況。
exit 0
樣例-24. 真實(shí)環(huán)境中的 continue N
# Albert Reiner 舉出了一個如何使用 "continue N" 的例子:
# ---------------------------------------------------
# 如果我有許多任務(wù)需要運(yùn)行伟件,并且運(yùn)行所需要的數(shù)據(jù)都以文件的形
#+ 式存在文件夾中∨鹌。現(xiàn)在有多臺設(shè)備可以訪問這個文件夾,我想將任
#+ 務(wù)分配給這些不同的設(shè)備來完成斧账。
# 那么我通常會在每臺設(shè)備上執(zhí)行下面的代碼:
while true:
do
for n in .iso.*
do
[ "$n" = ".iso.opts" ] && continue
beta=${n#.iso.}
[ -r .Iso.$beta ] && continue
[ -r .lock.$beta ] && sleep 10 && continue
lockfile -r0 .lock.$beta || continue
echo -n "$beta: " `date`
run-isotherm $beta
date
ls -alF .Iso.$beta
[ -r .Iso.$beta ] && rm -rf .lock.$beta
continue 2
done
break
done
exit 0
# 這個腳本中出現(xiàn)的 sleep N 只針對這個腳本谴返,通常的形式是:
while true
do
for job in {pattern}
do
{job already done or running} && continue
{mark job as running, do job, mark job as done}
continue 2
done
break # 或者使用類似 `sleep 600` 這樣的語句來防止腳本結(jié)束煞肾。
done
# 這樣做可以保證腳本只會在沒有任務(wù)時(包括在運(yùn)行過程中添加的任務(wù))
#+ 才會停止。合理使用文件鎖保證多臺設(shè)備可以無重復(fù)的并行執(zhí)行任務(wù)(這
#+ 在我的設(shè)備上通常會消耗好幾個小時嗓袱,所以我想避免重復(fù)計算)籍救。并且,
#+ 因?yàn)槊看慰偸菑念^開始搜索文件渠抹,因此可以通過文件名決定執(zhí)行的先后
#+ 順序蝙昙。當(dāng)然,你可以不使用 'continue 2' 來完成這些梧却,但是你必須
#+ 添加代碼去檢測某項(xiàng)任務(wù)是否完成(以此判斷是否可以執(zhí)行下一項(xiàng)任務(wù)或
#+ 終止奇颠、休眠一段時間再執(zhí)行下一項(xiàng)任務(wù))。
continue N
結(jié)構(gòu)不易理解并且可能在一些情況下有歧義放航,因此不建議使用烈拒。
測試與分支
case
和 select
結(jié)構(gòu)并不屬于循環(huán)結(jié)構(gòu),因?yàn)樗鼈儾]有反復(fù)執(zhí)行代碼塊三椿。但是和循環(huán)結(jié)構(gòu)相似的是,它們會根據(jù)代碼塊頂部或尾部的條件控制程序流葫辐。
下面介紹兩種在代碼塊中控制程序流的方法:
case (in) / esac
在 shell 腳本中搜锰,case
模擬了 C/C++
語言中的 switch
,可以根據(jù)條件跳轉(zhuǎn)到其中一個分支耿战。其相當(dāng)于簡寫版的 if/then/else
語句蛋叼。很適合用來創(chuàng)建菜單選項(xiàng)喲!
case "$variable" in
"$condition1" )
command...
;;
"$condition2" )
command...
;;
esac
對變量進(jìn)行引用不是必須的剂陡,因?yàn)樵谶@里不會進(jìn)行字符分割
條件測試語句必須以右括號 ) 結(jié)束
每一段代碼塊都必須以雙分號
;;
結(jié)束如果測試條件為真狈涮,其對應(yīng)的代碼塊將被執(zhí)行,而后整個
case
代碼段結(jié)束執(zhí)行鸭栖。case
代碼段必須以esac
結(jié)束(倒著拼寫case
)
樣例-25. 如何使用 case
#!/bin/bash
# 測試字符的種類歌馍。
echo; echo "Hit a key, then hit return."
read Keypress
case "$Keypress" in
[[:lower:]] ) echo "Lowercase letter";;
[[:upper:]] ) echo "Uppercase letter";;
[0-9] ) echo "Digit";;
* ) echo "Punctuation, whitespace, or other";;
esac # 字符范圍可以用[方括號]表示,也可以用 POSIX 形式的[[雙方括號]]表示晕鹊。
# 在這個例子的第一個版本中松却,用來測試是小寫還是大寫字符使用的是 [a-z] 和 [A-Z]。
# 這在一些特定的語言環(huán)境和 Linux 發(fā)行版中不起效溅话。
# POSIX 形式具有更好的兼容性晓锻。
# 感謝 Frank Wang 指出這一點(diǎn)。
# 練習(xí):
# -----
# 這個腳本接受一個單字符然后結(jié)束飞几。
# 修改腳本砚哆,使得其可以循環(huán)接受輸入,并且檢測鍵入的每一個字符屑墨,直到鍵入 "X" 為止躁锁。
# 提示:將所有東西包在 "while" 中纷铣。
exit 0
樣例-26. 使用 case
創(chuàng)建菜單
#!/bin/bash
# 簡易的通訊錄數(shù)據(jù)庫
clear # 清屏。
echo " Contact List"
echo " ------- ----"
echo "Choose one of the following persons:"
echo
echo "[E]vans, Roland"
echo "[J]ones, Mildred"
echo "[S]mith, Julie"
echo "[Z]ane, Morris"
echo
read person
case "$person" in
# 注意變量是被引用的灿里。
"E" | "e" )
# 同時接受大小寫的輸入关炼。
echo
echo "Roland Evans"
echo "4321 Flash Dr."
echo "Hardscrabble, CO 80753"
echo "(303) 734-9874"
echo "(303) 734-9892 fax"
echo "revans@zzy.net"
echo "Business partner & old friend"
;;
# 注意用雙分號結(jié)束這一個選項(xiàng)。
"J" | "j" )
echo
echo "Mildred Jones"
echo "249 E. 7th St., Apt. 19"
echo "New York, NY 10009"
echo "(212) 533-2814"
echo "(212) 533-9972 fax"
echo "milliej@loisaida.com"
echo "Ex-girlfriend"
echo "Birthday: Feb. 11"
;;
# Smith 和 Zane 的信息稍后添加匣吊。
* )
# 缺省設(shè)置儒拂。
# 空輸入(直接鍵入回車)也是執(zhí)行這一部分。
echo
echo "Not yet in database."
;;
esac
echo
# 練習(xí):
# -----
# 修改腳本色鸳,使得其可以循環(huán)接受多次輸入而不是只顯示一個地址后終止腳本社痛。
exit 0
你可以用 case
來檢測命令行參數(shù)
#!/bin/bash
case "$1" in
"") echo "Usage: ${0##*/} <filename>"; exit $E_PARAM;;
# 沒有命令行參數(shù),或者第一個參數(shù)為空命雀。
# 注意 ${0##*/} 是參數(shù)替換 ${var##pattern} 的一種形式蒜哀。
# 最后的結(jié)果是 $0.
-*) FILENAME=./$1;; # 如果傳入的參數(shù)以短橫線開頭,那么將其替換為 ./$1
#+ 以避免后續(xù)的命令將其解釋為一個選項(xiàng)吏砂。
* ) FILENAME=$1;; # 否則賦值為 $1撵儿。
esac
下面是一個更加直觀的處理命令行參數(shù)的例子:
#!/bin/bash
while [ $# -gt 0 ]; do # 遍歷完所有參數(shù)
case "$1" in
-d|--debug)
# 檢測是否是 "-d" 或者 "--debug"。
DEBUG=1
;;
-c|--conf)
CONFFILE="$2"
shift
if [ ! -f $CONFFILE ]; then
echo "Error: Supplied file doesn't exist!"
exit $E_CONFFILE # 找不到文件狐血。
fi
;;
esac
shift # 檢測下一個參數(shù)
done
樣例-27. 使用命令替換生成 case
變量
#!/bin/bash
# case-cmd.sh: 使用命令替換生成 "case" 變量淀歇。
case $( arch ) in # $( arch ) 返回設(shè)備架構(gòu)。
# 等價于 'uname -m"匈织。
i386 ) echo "80386-based machine";;
i486 ) echo "80486-based machine";;
i586 ) echo "Pentium-based machine";;
i686 ) echo "Pentium2+-based machine";;
* ) echo "Other type of machine";;
esac
exit 0
case
還可以用來做字符串模式匹配
樣例-28. 簡單的字符串匹配
#!/bin/bash
# match-string.sh: 使用 'case' 結(jié)構(gòu)進(jìn)行簡單的字符串匹配浪默。
match_string ()
{ # 字符串精確匹配。
MATCH=0
E_NOMATCH=90
PARAMS=2 # 需要2個參數(shù)缀匕。
E_BAD_PARAMS=91
[ $# -eq $PARAMS ] || return $E_BAD_PARAMS
case "$1" in
"$2") return $MATCH;;
* ) return $E_NOMATCH;;
esac
}
a=one
b=two
c=three
d=two
match_string $a # 參數(shù)個數(shù)不夠
echo $? # 91
match_string $a $b # 匹配不到
echo $? # 90
match_string $a $d # 匹配成功
echo $? # 0
exit 0
樣例-29. 檢查輸入
#!/bin/bash
# isaplpha.sh: 使用 "case" 結(jié)構(gòu)檢查輸入纳决。
SUCCESS=0
FAILURE=1 # 以前是FAILURE=-1,
#+ 但現(xiàn)在 Bash 不允許返回負(fù)值。
isalpha () # 測試字符串的第一個字符是否是字母乡小。
{
if [ -z "$1" ] # 檢測是否傳入?yún)?shù)阔加。
then
return $FAILURE
fi
case "$1" in
[a-zA-Z]*) return $SUCCESS;; # 是否以字母形式開始?
* ) return $FAILURE;;
esac
} # 可以與 C 語言中的函數(shù) "isalpha ()" 作比較满钟。
isalpha2 () # 測試整個字符串是否都是字母掸哑。
{
[ $# -eq 1 ] || return $FAILURE
case $1 in
*[!a-zA-Z]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
isdigit () # 測試整個字符串是否都是數(shù)字。
{ # 換句話說零远,也就是測試是否是一個整型變量苗分。
[ $# -eq 1 ] || return $FAILURE
case $1 in
*[!0-9]*|"") return $FAILURE;;
*) return $SUCCESS;;
esac
}
check_var () # 包裝后的 isalpha ()。
{
if isalpha "$@"
then
echo "\"$*\" begins with an alpha character."
if isalpha2 "$@"
then # 其實(shí)沒必要檢查第一個字符是不是字母牵辣。
echo "\"$*\" contains only alpha characters."
else
echo "\"$*\" contains at least one non-alpha character."
fi
else
echo "\"$*\" begins with a non-alpha character."
# 如果沒有傳入?yún)?shù)同樣同樣返回“存在非字母”摔癣。
fi
echo
}
digit_check () # 包裝后的 isdigit ()。
{
if isdigit "$@"
then
echo "\"$*\" contains only digits [0 - 9]."
else
echo "\"$*\" has at least one non-digit character."
fi
echo
}
a=23skidoo
b=H3llo
c=-What?
d=What?
e=$(echo $b) # 命令替換。
f=AbcDef
g=27234
h=27a34
i=27.34
check_var $a
check_var $b
check_var $c
check_var $d
check_var $e
check_var $f
check_var # 如果不傳入?yún)?shù)會發(fā)送什么择浊?
#
digit_check $g
digit_check $h
digit_check $i
exit 0
# 練習(xí):
# -----
# 寫一個函數(shù) 'isfloat ()' 來檢測輸入值是否是浮點(diǎn)數(shù)戴卜。
# 提示:可以參考函數(shù) 'isdigit ()',在其中加入檢測合法的小數(shù)點(diǎn)即可琢岩。
select
select
構(gòu)建菜單
select variable [in list]
do
command...
break
done
而效果則是終端會提示用戶輸入列表中的一個選項(xiàng)投剥。注意,select
默認(rèn)使用提示字串3(Prompt String 3担孔,$PS3, 即#?)江锨,但同樣可以被修改
樣例 11-30. 使用 select
創(chuàng)建菜單
#!/bin/bash
PS3='Choose your favorite vegetable: ' # 設(shè)置提示字串。
# 否則默認(rèn)為 #?糕篇。
echo
select vegetable in "beans" "carrots" "potatoes" "onions" "rutabagas"
do
echo
echo "Your favorite veggie is $vegetable."
echo "Yuck!"
echo
break # 如果沒有 'break' 會發(fā)生什么啄育?
done
exit
# 練習(xí):
# -----
# 修改腳本,使得其可以接受其他輸入而不是 "select" 語句中所指定的拌消。
# 例如挑豌,如果用戶輸入 "peas,",那么腳本會通知用戶 "Sorry. That is not on the menu."
如果 in list
被省略墩崩,那么 select
將會使用傳入腳本的命令行參數(shù) $@
或者傳入函數(shù)的參數(shù)作為 list
可以與 for variable in list
中 in list
被省略的情況做比較
樣例-31. 在函數(shù)中使用 select
創(chuàng)建菜單
#!/bin/bash
PS3='Choose your favorite vegetable: '
echo
choice_of()
{
select vegetable
# [in list] 被省略氓英,因此 'select' 將會使用傳入函數(shù)的參數(shù)作為 list。
do
echo
echo "Your favorite veggie is $vegetable."
echo "Yuck!"
echo
break
done
}
choice_of beans rice carrorts radishes rutabaga spinach
# $1 $2 $3 $4 $5 $6
# 傳入了函數(shù) choice_of()
exit 0