> For the complete documentation index, see [llms.txt](https://inityun.gitbook.io/niao-ge-de-linux-si-fang-cai-1/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://inityun.gitbook.io/niao-ge-de-linux-si-fang-cai-1/chapter_12/12.5-xun-huan-loop.md).

# 12.5 循环loop

除了 if...then...fi 这种条件判断式之外，循环可能是程序当中最重要的一环了～ 循环可以不断的执行某个程序段落，直到使用者设置的条件达成为止。 所以，重点是那个“条件的达成”是什么。除了这种依据判断式达成与否的不定循环之外， 还有另外一种已经固定要跑多少次的循环形态，可称为固定循环的形态呢！下面我们就来谈一谈：

## 12.5.1 while do done, until do done （不定循环）

一般来说，不定循环最常见的就是下面这两种状态了：

```shell
while [ condition ]  <==中括号内的状态就是判断式
do            <==do 是循环的开始！
    程序段落
done          <==done 是循环的结束
```

while 的中文是“当....时”，所以，这种方式说的是“当 condition 条件成立时，就进行循环，直到 condition 的条件不成立才停止”的意思。还有另外一种不定循环的方式：

```shell
until [ condition ]
do
    程序段落
done
```

这种方式恰恰与 while 相反，它说的是“当 condition 条件成立时，就终止循环， 否则就持续进行循环的程序段。”是否刚好相反啊～我们以 while 来做个简单的练习好了。 假设我要让使用者输入 yes 或者是 YES 才结束程序的执行，否则就一直进行告知使用者输入字串。

```shell
[dmtsai@study bin]$ vim yes_to_stop.sh
#!/bin/bash
# Program:
#    Repeat question until user input correct answer.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

while [ "${yn}" != "yes" -a "${yn}" != "YES" ]
do
    read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."
```

上面这个例题的说明是“当 ${yn} 这个变量不是 "yes" 且 ${yn} 也不是 "YES" 时，才进行循环内的程序。” 而如果 ${yn} 是 "yes" 或 "YES" 时，就会离开循环啰～那如果使用 until 呢？呵呵有趣啰～ 他的条件会变成这样：

```shell
[dmtsai@study bin]$ vim yes_to_stop-2.sh
#!/bin/bash
# Program:
#    Repeat question until user input correct answer.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

until [ "${yn}" == "yes" -o "${yn}" == "YES" ]
do
    read -p "Please input yes/YES to stop this program: " yn
done
echo "OK! you input the correct answer."
```

仔细比对一下这两个东西有啥不同喔！ ^\_^再来，如果我想要计算 1+2+3+....+100 这个数据呢？ 利用循环啊～他是这样的：

```shell
[dmtsai@study bin]$ vim cal_1_100.sh
#!/bin/bash
# Program:
#    Use loop to calculate "1+2+3+...+100" result.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

s=0  # 这是加总的数值变量
i=0  # 这是累计的数值，亦即是 1, 2, 3....
while [ "${i}" != "100" ]
do
    i=$（（$i+1））   # 每次 i 都会增加 1 
    s=$（（$s+$i））  # 每次都会加总一次！
done
echo "The result of '1+2+3+...+100' is ==> $s"
```

嘿嘿！当你执行了“ sh cal\_1\_100.sh ”之后，就可以得到 5050 这个数据才对啊！这样瞭呼～ 那么让你自行做一下，如果想要让使用者自行输入一个数字，让程序由 1+2+... 直到你输入的数字为止， 该如何撰写呢？应该很简单吧？答案可以参考一下\[习题练习]里面的一题喔！

## 12.5.2 for...do...done （固定循环）

相对于 while, until 的循环方式是必须要“符合某个条件”的状态， for 这种语法，则是“ 已经知道要进行几次循环”的状态！他的语法是：

```shell
for var in con1 con2 con3 ...
do
    程序段
done
```

以上面的例子来说，这个 $var 的变量内容在循环工作时：

1. 第一次循环时， $var 的内容为 con1 ；
2. 第二次循环时， $var 的内容为 con2 ；
3. 第三次循环时， $var 的内容为 con3 ；
4. ....

我们可以做个简单的练习。假设我有三种动物，分别是 dog, cat, elephant 三种， 我想每一行都输出这样：“There are dogs...”之类的字样，则可以：

```shell
[dmtsai@study bin]$ vim show_animal.sh
#!/bin/bash
# Program:
#     Using for .... loop to print 3 animals
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

for animal in dog cat elephant
do
    echo "There are ${animal}s.... "
done
```

等你执行之后就能够发现这个程序运行的情况啦！让我们想像另外一种状况，由于系统上面的各种帐号都是写在 /etc/passwd 内的第一个字段，你能不能通过管线命令的 \[cut] 捉出单纯的帐号名称后，以 \[id] 分别检查使用者的识别码与特殊参数呢？由于不同的 Linux 系统上面的帐号都不一样！此时实际去捉 /etc/passwd 并使用循环处理，就是一个可行的方案了！程序可以如下：

```shell
[dmtsai@study bin]$ vim userid.sh
#!/bin/bash
# Program
#       Use id, finger command to check system account's information.
# History
# 2015/07/17    VBird   first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
users=$（cut -d ':' -f1 /etc/passwd）    # 撷取帐号名称
for username in ${users}               # 开始循环进行！
do
        id ${username}
done
```

执行上面的脚本后，你的系统帐号就会被捉出来检查啦！这个动作还可以用在每个帐号的删除、重整上面呢！ 换个角度来看，如果我现在需要一连串的数字来进行循环呢？举例来说，我想要利用 ping 这个可以判断网络状态的指令， 来进行网络状态的实际侦测时，我想要侦测的网域是本机所在的 192.168.1.1\~192.168.1.100，由于有 100 台主机， 总不会要我在 for 后面输入 1 到 100 吧？此时你可以这样做喔！

```shell
[dmtsai@study bin]$ vim pingip.sh
#!/bin/bash
# Program
#       Use ping command to check the network's PC state.
# History
# 2015/07/17    VBird   first release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
network="192.168.1"              # 先定义一个网域的前面部分！
for sitenu in $（seq 1 100）       # seq 为 sequence（连续） 的缩写之意
do
    # 下面的程序在取得 ping 的回传值是正确的还是失败的！
        ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 &#124;&#124; result=1
    # 开始显示结果是正确的启动 （UP） 还是错误的没有连通 （DOWN）
        if [ "${result}" == 0 ]; then
                echo "Server ${network}.${sitenu} is UP."
        else
                echo "Server ${network}.${sitenu} is DOWN."
        fi
done
```

上面这一串指令执行之后就可以显示出 192.168.1.1\~192.168.1.100 共 100 部主机目前是否能与你的机器连通！ 如果你的网域与鸟哥所在的位置不同，则直接修改上头那个 network 的变量内容即可！其实这个范例的重点在 $（seq ..） 那个位置！那个 seq 是连续 （sequence） 的缩写之意！代表后面接的两个数值是一直连续的！ 如此一来，就能够轻松的将连续数字带入程序中啰！

**Tips** 除了使用 $（seq 1 100） 之外，你也可以直接使用 bash 的内置机制来处理喔！可以使用 {1..100} 来取代 $（seq 1 100） ！ 那个大括号内的前面/后面用两个字符，中间以两个小数点来代表连续出现的意思！例如要持续输出 a, b, c...g 的话， 就可以使用“ echo {a..g} ”这样的表示方式！

最后，让我们来玩判断式加上循环的功能！我想要让使用者输入某个目录文件名， 然后我找出某目录内的文件名的权限，该如何是好？呵呵！可以这样做啦～

```shell
[dmtsai@study bin]$ vim dir_perm.sh
#!/bin/bash
# Program:
#    User input dir name, I find the permission of files.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

# 1\. 先看看这个目录是否存在啊？
read -p "Please input a directory: " dir
if [ "${dir}" == "" -o ! -d "${dir}" ]; then
    echo "The ${dir} is NOT exist in your system."
    exit 1
fi

# 2\. 开始测试文件啰～
filelist=$（ls ${dir}）        # 列出所有在该目录下的文件名称
for filename in ${filelist}
do
    perm=""
    test -r "${dir}/${filename}" && perm="${perm} readable"
    test -w "${dir}/${filename}" && perm="${perm} writable"
    test -x "${dir}/${filename}" && perm="${perm} executable"
    echo "The file ${dir}/${filename}'s permission is ${perm} "
done
```

呵呵！很有趣的例子吧～利用这种方式，你可以很轻易的来处理一些文件的特性呢。接下来，让我们来玩玩另一种 for 循环的功能吧！主要用在数值方面的处理喔！

## 12.5.3 for...do...done 的数值处理

除了上述的方法之外，for 循环还有另外一种写法！语法如下：

```shell
for （（ 初始值; 限制值; 执行步阶 ））
do
    程序段
done
```

这种语法适合于数值方式的运算当中，在 for 后面的括号内的三串内容意义为：

* 初始值：某个变量在循环当中的起始值，直接以类似 i=1 设置好；
* 限制值：当变量的值在这个限制值的范围内，就继续进行循环。例如 i<=100；
* 执行步阶：每作一次循环时，变量的变化量。例如 i=i+1。

值得注意的是，在“执行步阶”的设置上，如果每次增加 1 ，则可以使用类似“i++”的方式，亦即是 i 每次循环都会增加一的意思。好，我们以这种方式来进行 1 累加到使用者输入的循环吧！

```shell
[dmtsai@study bin]$ vim cal_1_100-2.sh
#!/bin/bash
# Program:
#     Try do calculate 1+2+....+${your_input}
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

read -p "Please input a number, I will count for 1+2+...+your_input: " nu

s=0
for （（ i=1; i<=${nu}; i=i+1 ））
do
    s=$（（${s}+${i}））
done
echo "The result of '1+2+3+...+${nu}' is ==> ${s}"
```

一样也是很简单吧！利用这个 for 则可以直接限制循环要进行几次呢！

## 12.5.4 搭配乱数与阵列的实验

现在你大概已经能够掌握 shell script 了！好了！让我们来做个小实验！假设你们公司的团队中，经常为了今天中午要吃啥搞到头很昏！ 每次都用猜拳的～好烦喔～有没有办法写支脚本，用脚本搭配乱数来告诉我们，今天中午吃啥好？呵呵！执行这只脚本后， 直接跟你说要吃啥～那比猜拳好多了吧？哈哈！

要达成这个任务，首先你得要将全部的店家输入到一组阵列当中，再通过乱数的处理，去取得可能的数值，再将搭配到该数值的店家秀出来即可！ 其实也很简单！让我们来实验看看：

```shell
[dmtsai@study bin]$ vim what_to_eat.sh
#!/bin/bash
# Program:
#     Try do tell you what you may eat.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

eat[1]="卖当当漢堡包"       # 写下你所收集到的店家！
eat[2]="肯爷爷炸鸡"
eat[3]="彩虹日式便当"
eat[4]="越油越好吃大雅"
eat[5]="想不出吃啥学餐"
eat[6]="太师父便当"
eat[7]="池上便当"
eat[8]="怀念火车便当"
eat[9]="一起吃方便面"
eatnum=9                  # 需要输入有几个可用的餐厅数！

check=$（（ ${RANDOM} * ${eatnum} / 32767 + 1 ））
echo "your may eat ${eat[${check}]}"
```

立刻执行看看，你就知道该吃啥了！非常有趣吧！不过，这个例子中只选择一个样本，不够看！如果想要每次都秀出 3 个店家呢？ 而且这个店家不能重复喔！重复当然就没啥意义了！所以，你可以这样作！

```shell
[dmtsai@study bin]$ vim what_to_eat-2.sh
#!/bin/bash
# Program:
#     Try do tell you what you may eat.
# History:
# 2015/07/17    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH

eat[1]="卖当当漢堡包"
eat[2]="肯爷爷炸鸡"
eat[3]="彩虹日式便当"
eat[4]="越油越好吃大雅"
eat[5]="想不出吃啥学餐"
eat[6]="太师父便当"
eat[7]="池上便当"
eat[8]="怀念火车便当"
eat[9]="一起吃方便面"
eatnum=9

eated=0
while [ "${eated}" -lt 3 ]; do
        check=$（（ ${RANDOM} * ${eatnum} / 32767 + 1 ））
        mycheck=0
        if [ "${eated}" -ge 1 ]; then
                for i in $（seq 1 ${eated} ）
                do
                        if [ ${eatedcon[$i]} == $check ]; then
                                mycheck=1
                        fi
                done
        fi
        if [ ${mycheck} == 0 ]; then
                echo "your may eat ${eat[${check}]}"
                eated=$（（ ${eated} + 1 ））
                eatedcon[${eated}]=${check}
        fi
done
```

通过乱数、阵列、循环与条件判断，你可以做出很多很特别的东西！还不用写传统程序语言～试看看～挺有趣的呦！
