# 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
```

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://inityun.gitbook.io/niao-ge-de-linux-si-fang-cai-1/chapter_12/12.5-xun-huan-loop.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
