Bash脚本编程学习笔记07:循环结构体
本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文《Bash脚本编程学习笔记03:算术运算》中我有说明不要使用,不过自己忘记了。大家还是尽量使用其他的方法进行算术运算。
简介
Bash具有三种循环结构:
- for循环。
- while循环。
- untile循环。
在使用循环结构体的时候,需要注意循环的进入条件和结束条件,避免出现死循环的情况。
for循环
for循环又分为两种格式:遍历列表和控制变量。
遍历列表
- for VAR in LIST; do
- BODY
- done
VAR:变量,在每次循环时,都会被LIST中的元素所赋值。
LIST:列表,生成列表的方式有多种,后面会详述。
BODY:循环体,由各种各样的命令所构成,在循环体中会引用变量VAR。
进入循环条件:只要LIST中有元素即可。
离开循环条件:LIST中的元素遍历完毕。
LIST的生成方式
这里的LIST生成方式的分类并非官方,甚至看起来蛮重复的,大家实际敲过之后就大概能明白列表是个什么东西了。
官方的LIST其实就是shell展开。
1、直接给出。
- [root@c7-server ~]# cat for_list.sh
- #!/bin/bash
- for i in ; do
- echo $i
- done
- [root@c7-server ~]# bash for_list.sh
2、通过大括号或者seq命令生成的整数列表。
- [root@c7-server ~]# cat for_list.sh
- #!/bin/bash
- for i in {..}; do
- echo $i
- done
- [root@c7-server ~]# bash for_list.sh
seq是一个外部命令,一般用于生成一个整数序列。
- seq [FIRST [INCREMENT]] LAST
见名知意,大家试试以下几种命令生成的整数列表就懂了。
- # seq
- # seq
- # seq
- # seq
- # seq
seq在使用的时候要结合bash的命令替换机制,即下面要说的就是了。
3、引用返回列表的命令。
像刚才的seq其实就类似于这种,其他的例如应用ls命令的结果的也是可以。
4、Glob风格的展开。
----2020-01-13----
{x..y}:当x和y是正整数的时候,相比大家都会用,需要注意的是它可以加入一个增量。
- {x..y[..incr]}
示例如下:
- [root@c7-server ~]# echo {..}
- [root@c7-server ~]# echo {....}
5、位置参数($@, $*)的引用。
练习
1、编写一个脚本,批量创建5个用户。
- #!/bin/bash
- for i in zwl0{..}; do
- if id $i &> /dev/null; then
- echo "User $i has already exists."
- else
- useradd $i
- fi
- done
2、编写一个脚本,统计100以内的整数之和、奇数之和与偶数之和。
整数之和。
- #!/bin/bash
- declare -i sum=
- for i in $(seq 100); do
- echo "sum is $sum, i is $i."
- sum=$[$sum+$i]
- done
- echo "sum is $sum."
奇数之和:只要将seq替换为“seq 1 2 100”即可。
偶数之和:只要将seq替换为“seq 2 2 100”即可。
3、编写一个脚本,判断某目录下的所有文件的类型。
- #!/bin/bash
- for i in /root/*; do
- file $i
- done
其实file命令本身即可实现,主要是了解一下可以以通配符展开来生成LIST。
- # file /root/*
4、计算当前所有用户的UID之和。
- #!/bin/bash
- declare -i sum=
- for i in $(cut -d : -f /etc/passwd); do
- sum=$[$sum+$i]
- done
- echo "The sum of UIDs is $sum."
5、统计某个目录下的文本文件总数,以及文本文件的行数之和。注:无需递归,仅统计目录下第一层即可。
- #!/bin/bash
- if [ ! -d $ ]; then
- echo "The file you input is not a directory,exit!"
- exit
- fi
- declare -i textCount=
- declare -i lineCount=
- for i in $/*; do
- if [ -f $i ]; then
- lines=$(wc -l $i | cut -d " " -f 1)
- textCount=$[$textCount+1]
- lineCount=$[$lineCount+$lines]
- fi
- done
- echo "The number of text file is $textCount."
- echo "The number of line of text file is $lineCount."
控制变量
for语句的控制变量,其实就是类似于C风格的for语句。
- for ((expr1; expr2; expr3)); do
- BODY
- done
此类for语句,只用于数值类的计算,写起来更像C语言,在(())不需要再使用$对变量进行展开,写起来更简洁方便。
expr1:只有在第一次循环时执行,一般用于对某个变量进行赋初值的操作。
expr2:每次都会执行,一般是对赋值的变量进行条件判断,为真执行BODY;为假的话,结束循环。
expr3:对赋值的变量进行数值调整,使其将来满足expr2为假的情况从而结束循环。
示例:计算100以内所有奇数之和。
- #!/bin/bash
- declare -i sum=
- for ((i=;i<=;i+=)); do
- ((sum+=i))
- done
- echo "Sum is $sum."
while循环
- while CONDITION; do
- CMD
- done
当CONDITION为真时,执行CMD,直到CONDITION为假的时候才退出循环。
CMD中一般会包含一些可以在将来改变CONDITION的判定结果的操作,否则会出现死循环。
while循环相比for循环的优势在于,for循环需要事先生成一个列表,如果列表元素比较大,例如{1..10000},那么就会占用比较多的内存空间;而while循环只需要占用一个变量的内存空间即可。(结论来自马哥,我觉得应该没错吧)
我们使用while循环来实现计算100以内的正整数之和。
- [root@c7-server ~]# cat while_sum.sh
- #!/bin/bash
- declare -i sum=
- declare -i i=
- while [ $i -le ]; do
- ((sum+=i))
- ((i++))
- done
- echo "sum is $sum."
几个注意事项:
后缀自增/减运算和赋值(如:+=, -=, *=, /=等等)时,涉及变量的时候不要将变量展开,否则会报错。
- [root@c7-server ~]# declare -i i=
- [root@c7-server ~]# (($i++))
- -bash: ((: ++: syntax error: operand expected (error token is "+")
- [root@c7-server ~]# (($i+=))
- -bash: ((: +=: attempted assignment to non-variable (error token is "+=1")
同样的方式,在前缀自增/减时虽然不会报错,但是也不会达到预期的效果。
- [root@c7-server ~]# ((++$i))
- [root@c7-server ~]# echo $i
因为一旦使用了展开,bash会先进行展开,再进行算术运算。展开后就变成了。
- ((++))
- ((++))
- ((+=))
正确的用法是:
- ((i++))
- ((++i))
- ((i+=))
赋值时,等于号右边的变量,可以不展开。以下是等效的。
- ((a=a+b))
- ((a=$a+$b))
- ((a+=b))
- ((a+=$b))
特殊用法:遍历文件内容
while循环有一种特殊的用法,可以遍历文件的内容(以行为单位),进行处理。文件内容遍历完毕后退出。
- while read VAR; do
- BODY
- done < /PATH/FROM/SOMEFILE
示例:打印UID为偶数的用户的名称、UID和默认shell。
- #!/bin/bash
- while read line; do
- userName=$(echo $line | cut -d : -f )
- userID=$(echo $line | cut -d : -f )
- userShell=$(echo $line | cut -d : -f )
- if [ $((userID%)) -eq ]; then
- echo "User name is $userName. User ID is $userID. User shell is $userShell."
- fi
- done < /etc/passwd
使用for+cat命令替换也可以实现类似的功能。
- for i in $(cat /PATH/TO/SOMEFILE); do
- echo $i
- done
文件中的每一行也会被赋值给i,但是如果行内存在空格,那么那一行会被理解为多行。因此比较稳妥的方法还是使用while的特殊用法。
until循环
- untile CONDITION; do
- CMD
- done
与while循环的进入循环和退出循环的逻辑正好相反。当CONDITION为假时,执行CMD,直到CONDITION为真的时候才退出循环。
同样,CMD中一般会包含一些可以在将来改变CONDITION的判定结果的操作,否则会出现死循环。
until循环和while循环只是逻辑相反,因此用的比较少,while比较常用。
同样我们也使用until循环实现100以内的正整数之和的计算。
- #!/bin/bash
- declare -i sum=
- declare -i i=
- until [ $i -gt ]; do
- ((sum+=i))
- ((i++))
- done
- echo "sum is $sum."
练习
1、使用四种循环结构体实现乘法口诀表的正向和反向打印。
for循环正向打印。
- [root@c7-server ~]# cat for_jiujiu.sh
- #!/bin/bash
- for i in {..}; do
- for j in $(seq $i); do
- echo -ne "$j*$i=$((i*j))\t"
- done
- echo
- done
- [root@c7-server ~]# bash for_jiujiu.sh
- *=
- *= *=
- *= *= *=
- *= *= *= *=
- *= *= *= *= *=
- *= *= *= *= *= *=
- *= *= *= *= *= *= *=
- *= *= *= *= *= *= *= *=
- *= *= *= *= *= *= *= *= *=
这里需要注意的一点,是:
- for j in $(seq $i)
不可以换成
- for j in {..$i}
具体示例如下。
- [root@c7-server ~]# echo {..}
- [root@c7-server ~]# declare -i i=
- [root@c7-server ~]# echo {..$i}
- {..}
造成此结果的原因,在官方的关于大括号展开中有提及。
Brace expansion is performed before any other expansions, and any characters special to other expansions are preserved in the result. It is strictly textual. Bash does not apply any syntactic interpretation to the context of the expansion or the text between the braces.
for循环的反向打印,只要将{1..9}换成{9..1}即可。
- [root@c7-server ~]# bash for_jiujiu.sh
- *= *= *= *= *= *= *= *= *=
- *= *= *= *= *= *= *= *=
- *= *= *= *= *= *= *=
- *= *= *= *= *= *=
- *= *= *= *= *=
- *= *= *= *=
- *= *= *=
- *= *=
- *=
C风格的for循环的正向打印。
- #!/bin/bash
- for ((i=;i<=;i++)); do
- for ((j=;j<=i;j++)); do
- echo -ne "$j*$i=$((i*j))\t"
- done
- echo
- done
C风格的for循环的反向打印。
- #!/bin/bash
- for ((i=;i>=;i--)); do
- for ((j=;j<=i;j++)); do
- echo -ne "$j*$i=$((i*j))\t"
- done
- echo
- done
while循环正向打印。
- #!/bin/bash
- declare -i i=
- while [ $i -le ]; do
- declare -i j=
- while [ $j -le $i ]; do
- echo -ne "$j*$i=$((i*j))\t"
- ((j++))
- done
- ((i++))
- echo
- done
while循环反向打印。
- #!/bin/bash
- declare -i i=
- while [ $i -gt ]; do
- declare -i j=
- while [ $j -le $i ]; do
- echo -ne "$j*$i=$((i*j))\t"
- ((j++))
- done
- ((i--))
- echo
- done
until循环正向打印。
- #!/bin/bash
- declare -i sum=
- declare -i i=
- until [ $i -gt ]; do
- declare -i j=
- until [ $j -gt $i ]; do
- echo -ne "$j*$i=$((i*j))\t"
- ((j++))
- done
- ((i++))
- echo
- done
until循环反向打印。
- #!/bin/bash
- declare -i sum=
- declare -i i=
- until [ $i -le ]; do
- declare -i j=
- until [ $j -gt $i ]; do
- echo -ne "$j*$i=$((i*j))\t"
- ((j++))
- done
- ((i--))
- echo
- done
循环控制指令
此前讲解循环时,循环的每一轮,都是要执行的,直到循环退出条件满足时才退出。而循环控制指令continue和break,可以改变这种默认的机制。
continue:结束本轮循环,进入下一轮循环。大致结构如下。
- for i in LIST; do
- CMD1
- ...
- if TEST; then
- continue
- fi
- CMDn
- ...
- done
进入下一轮循环的条件是本轮循环的BODY部分全部执行完,因此continue不应该放在整个BODY的末尾,否则就有点画蛇添足了。
continue一般放在BODY的中间,结合某些判断跳出本轮循环。例如,当LIST是100以内的所有正整数时,求所有偶数之和。
- #!/bin/bash
- declare -i sum=
- for i in {..}; do
- if [ $((i%)) -eq ]; then
- continue
- fi
- ((sum+=i))
- done
- echo "sum is $sum."
break:直接结束所有的循环,继续执行脚本剩余的部分。这里要注意和exit区分,如果break出现的位置换成了exit的话,那么exit结束的是整个脚本,而不仅仅是循环而已,脚本直接退出了,不会执行脚本剩余部分。
break一般会结合死循环一起使用,死循环一般会结合sleep命令一起使用。
sleep命令基本语法。
- sleep n[suffix]
n:具体的数值,默认单位是秒。
suffix:后缀,表示单位。s秒、n分钟、h小时、d天数。
大致的语法就是这个样子,整个循环是一个死循环。sleep控制了死循环的循环间隔,防止消耗资源过多;if+break实现了对死循环的控制,达到某个条件就退出。
- while true; do
- CMD1
- ...
- if TEST; then
- break
- fi
- [CMDn
- ...]
- sleep ...
- done
使用死循环+break求100以内所有奇数之和。
- #!/bin/bash
- declare -i i=
- declare -i sum=
- while true; do
- if [ $i -gt ]; then
- break
- fi
- ((sum+=i))
- ((i+=))
- done
- echo "sum is $sum."
练习:每隔3秒监控系统中已登录的用户,如果发现alongdidi则记录于日志中并退出脚本。
思路一:死循环监控
- [root@c7-server ~]# cat user_monitor.sh
- #!/bin/bash
- while true; do
- if who | grep "^alongdidi\>" &> /dev/null; then
- echo "$(date +"%F %T") alongdidi logined." >> /var/log/user_monitor.log
- break
- else
- sleep
- fi
- done
- [root@c7-server ~]# cat /var/log/user_monitor.log
- -- :: alongdidi logined.
思路二:直到alongdidi登录,否则继续循环。
- #!/bin/bash
- until who | grep "^alongdidi\>" &> /dev/null; do
- sleep
- done
- echo "$(date +"%F %T") alongdidi logined." >> /var/log/user_monitor.log
continue和break在使用的时候可以带上参数n。
continue n:跳出n轮循环。
break n:结束几个循环体。这种在嵌套循环的情况下才会遇到。
- while ...; do
- while ...; do
- break
- done
- done
Bash脚本编程学习笔记07:循环结构体的更多相关文章
- Bash脚本编程学习笔记06:条件结构体
简介 在bash脚本编程中,条件结构体使用if语句和case语句两种句式. if语句 单分支if语句 if TEST; then CMD fi TEST:条件判断,多数情况下可使用test命令来实现, ...
- Bash脚本编程学习笔记08:函数
官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...
- Bash脚本编程学习笔记05:用户交互与脚本调试
用户交互 在<学习笔记04>中我们有提到位置参数,位置参数是用来向脚本传递参数的一种方式.还有一种方式,是read命令. [root@c7-server ~]# read name alo ...
- Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量
我自己接触Linux主要是大学学习的Turbolinux --> 根据<鸟哥的Linux私房菜:基础篇>(第三版) --> 马哥的就业班课程.给我的感觉是这些课程对于bash的 ...
- bash脚本编程学习笔记(一)
bash脚本语言,不同于C/C++是一种解释性语言.即在执行前不需要事先转变为可执行的二进制代码,而是每次执行时经解释器解释后执行.bash脚本语言是命令的堆砌,即按照实际需要,结合命令流程机制实现的 ...
- bash脚本编程学习笔记(二)
1.脚本编程之函数 函数是实现结构化编程重要的思想,主要目的是实现代码重用 定义一个函数: function FUNCNAME { command //函数体 } FUNCNAME(){ //函数 ...
- Go语言学习笔记十: 结构体
Go语言学习笔记十: 结构体 Go语言的结构体语法和C语言类似.而结构体这个概念就类似高级语言Java中的类. 结构体定义 结构体有两个关键字type和struct,中间夹着一个结构体名称.大括号里面 ...
- matlab学习笔记12_3串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields
一起来学matlab-matlab学习笔记12 12_3 结构体 串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields 觉得有用的话 ...
- Swift 学习笔记 (类和结构体)
类和结构体是一种多功能且灵活的构造体.通过使用与现存常量 变量 函数完全相同的语法来在类和结构体中定义属性和方法以添加功能. Swift中不需要你为自定义的类和结构体创建独立的结构和实现文件.在Swi ...
随机推荐
- Linux 常用工具sysstat之iostat
命令解释 用于输出CPU和磁盘I/O相关的统计信息:iostat依赖于sysstat软件包 命令格式 iostat [ 选项 ] [<时间间隔> [<次数>]] 常用选项 -c ...
- springboot mybatis 多数据源配置支持切换以及一些坑
一 添加每个数据源的config配置,单个直接默认,多个需要显示写出来 @Configuration @MapperScan(basePackages ="com.zhuzher.*.map ...
- Visual Studio 2012 出现关于ActivityLog.xml错误的解决方案
由sp1升级sp2后出现的错误. devenv.exe /safemode启动下,就可以了 命令列參數 描述 /Command (devenv.exe) 啟動 IDE 並執行指定的命令. /Debug ...
- Java.work7 访问权限、对象使用作业20194651
题目1: 在作业5的基础上,再创建一个柱体类,包含矩形对象.高和体积等三个成员变量,一个构造方法进行成员变量初始化,和计算体积.换底两个功能方法,在主类中输入长.宽.高,计算柱体体积,输入新的长.宽. ...
- Codeforces 961C Chessboard(将碎了的、染色乱了的棋盘碎片拼一起)
题目链接:点击打开链接 Magnus decided to play a classic chess game. Though what he saw in his locker shocked hi ...
- vs 搭配 Linux 开发
这是一篇翻译,为什么突然想翻译文章了呢,因为很多大佬们都说英语对程序员还是挺重要的,毕竟互联网的最新技术基本都在歪果仁那边,如果英语不好,不会看国外的文档的话,将会错失接触第一手资料的机会,失去很多先 ...
- Coroutine 预激装饰器
预激装饰器 讨论如何终止协程之前,我们要先谈谈如何启动协程.使用协程之前必须预激,可是这一 步容易忘记.为了避免忘记,可以在协程上使用一个特殊的装饰器.接下来介绍这样一个 装饰器. 预激协程的装饰器, ...
- qt creator源码全方面分析(2-3)
目录 External Tool Specification Files 文件名 位置 文件格式 主要标签 描述标签 可执行规范标签 示例 External Tool Specification Fi ...
- 开源堡垒机jumpserver
开源堡垒机jumpserver 开源堡垒机jumpserver的安装 开源堡垒机jumpserver的配置和使用
- k8s系列--- dashboard认证及分级授权
http://blog.itpub.net/28916011/viewspace-2215214/ 因版本不一样,略有改动 Dashboard官方地址: https://github.com/kube ...