12.5 循环 (loop)

最后更新于:2022-04-01 22:13:48

## 12.5 循环 (loop) 除了 if...then...fi 这种条件判断式之外,循环可能是程序当中最重要的一环了~ 循环可以不断的执行某个程序段落,直到使用者设置的条件达成为止。 所以,重点是那个“条件的达成”是什么。除了这种依据判断式达成与否的不定循环之外, 还有另外一种已经固定要跑多少次的循环形态,可称为固定循环的形态呢!下面我们就来谈一谈: ### 12.5.1 while do done, until do done (不定循环) 一般来说,不定循环最常见的就是下面这两种状态了: ``` while [ condition ] <==中括号内的状态就是判断式 do <==do 是循环的开始! 程序段落 done <==done 是循环的结束 ``` while 的中文是“当....时”,所以,这种方式说的是“当 condition 条件成立时,就进行循环,直到 condition 的条件不成立才停止”的意思。还有另外一种不定循环的方式: ``` until [ condition ] do 程序段落 done ``` 这种方式恰恰与 while 相反,它说的是“当 condition 条件成立时,就终止循环, 否则就持续进行循环的程序段。”是否刚好相反啊~我们以 while 来做个简单的练习好了。 假设我要让使用者输入 yes 或者是 YES 才结束程序的执行,否则就一直进行告知使用者输入字串。 ``` [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 呢?呵呵有趣啰~ 他的条件会变成这样: ``` [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 这个数据呢? 利用循环啊~他是这样的: ``` [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+... 直到你输入的数字为止, 该如何撰写呢?应该很简单吧?答案可以参考一下[习题练习](../Text/index.html#ex)里面的一题喔! ### 12.5.2 for...do...done (固定循环) 相对于 while, until 的循环方式是必须要“符合某个条件”的状态, for 这种语法,则是“ 已经知道要进行几次循环”的状态!他的语法是: ``` 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...”之类的字样,则可以: ``` [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](../Text/index.html#cut) 捉出单纯的帐号名称后,以 [id](../Text/index.html#id) 分别检查使用者的识别码与特殊参数呢?由于不同的 Linux 系统上面的帐号都不一样!此时实际去捉 /etc/passwd 并使用循环处理,就是一个可行的方案了!程序可以如下: ``` [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 吧?此时你可以这样做喔! ``` [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 || 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) 的缩写之意!代表后面接的两个数值是一直连续的! 如此一来,就能够轻松的将连续数字带入程序中啰! ![鸟哥的图示](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-05-13_5735736501917.gif "鸟哥的图示") **Tips** 除了使用 $(seq 1 100) 之外,你也可以直接使用 bash 的内置机制来处理喔!可以使用 {1..100} 来取代 $(seq 1 100) ! 那个大括号内的前面/后面用两个字符,中间以两个小数点来代表连续出现的意思!例如要持续输出 a, b, c...g 的话, 就可以使用“ echo {a..g} ”这样的表示方式! 最后,让我们来玩判断式加上循环的功能!我想要让使用者输入某个目录文件名, 然后我找出某目录内的文件名的权限,该如何是好?呵呵!可以这样做啦~ ``` [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 循环还有另外一种写法!语法如下: ``` for (( 初始值; 限制值; 执行步阶 )) do 程序段 done ``` 这种语法适合于数值方式的运算当中,在 for 后面的括号内的三串内容意义为: * 初始值:某个变量在循环当中的起始值,直接以类似 i=1 设置好; * 限制值:当变量的值在这个限制值的范围内,就继续进行循环。例如 i<=100; * 执行步阶:每作一次循环时,变量的变化量。例如 i=i+1。 值得注意的是,在“执行步阶”的设置上,如果每次增加 1 ,则可以使用类似“i++”的方式,亦即是 i 每次循环都会增加一的意思。好,我们以这种方式来进行 1 累加到使用者输入的循环吧! ``` [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 了!好了!让我们来做个小实验!假设你们公司的团队中,经常为了今天中午要吃啥搞到头很昏! 每次都用猜拳的~好烦喔~有没有办法写支脚本,用脚本搭配乱数来告诉我们,今天中午吃啥好?呵呵!执行这只脚本后, 直接跟你说要吃啥~那比猜拳好多了吧?哈哈! 要达成这个任务,首先你得要将全部的店家输入到一组阵列当中,再通过乱数的处理,去取得可能的数值,再将搭配到该数值的店家秀出来即可! 其实也很简单!让我们来实验看看: ``` [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 个店家呢? 而且这个店家不能重复喔!重复当然就没啥意义了!所以,你可以这样作! ``` [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 ``` 通过乱数、阵列、循环与条件判断,你可以做出很多很特别的东西!还不用写传统程序语言~试看看~挺有趣的呦!
';