Shell進(jìn)階腳本-循環(huán)與分支

循環(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 中允許含有通配符

如果 dofor 寫在同一行時卫漫,需要在 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)鍵字dodone 圈定了 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

如何在 forwhileuntil 之間做出選擇印屁?我們知道在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)中 breakcontinue 的作用

#!/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)不易理解并且可能在一些情況下有歧義放航,因此不建議使用烈拒。

測試與分支

caseselect 結(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 listin 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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹦筹,一起剝皮案震驚了整個濱河市铝阐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盛龄,老刑警劉巖饰迹,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芳誓,死亡現(xiàn)場離奇詭異余舶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锹淌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門匿值,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赂摆,你說我怎么就攤上這事挟憔。” “怎么了烟号?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵绊谭,是天一觀的道長。 經(jīng)常有香客問我汪拥,道長达传,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮宪赶,結(jié)果婚禮上宗弯,老公的妹妹穿的比我還像新娘。我一直安慰自己搂妻,他們只是感情好蒙保,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欲主,像睡著了一般邓厕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上岛蚤,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天邑狸,我揣著相機(jī)與錄音,去河邊找鬼涤妒。 笑死单雾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的她紫。 我是一名探鬼主播硅堆,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贿讹!你這毒婦竟也來了渐逃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤民褂,失蹤者是張志新(化名)和其女友劉穎茄菊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赊堪,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡面殖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了哭廉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脊僚。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖遵绰,靈堂內(nèi)的尸體忽然破棺而出辽幌,到底是詐尸還是另有隱情,我是刑警寧澤椿访,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布乌企,位于F島的核電站,受9級特大地震影響成玫,放射性物質(zhì)發(fā)生泄漏加酵。R本人自食惡果不足惜端辱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望虽画。 院中可真熱鬧舞蔽,春花似錦、人聲如沸码撰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脖岛。三九已至朵栖,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柴梆,已是汗流浹背陨溅。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绍在,地道東北人门扇。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像偿渡,于是被迫代替她去往敵國和親臼寄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容