本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文《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:循环结构体的更多相关文章

  1. Bash脚本编程学习笔记06:条件结构体

    简介 在bash脚本编程中,条件结构体使用if语句和case语句两种句式. if语句 单分支if语句 if TEST; then CMD fi TEST:条件判断,多数情况下可使用test命令来实现, ...

  2. Bash脚本编程学习笔记08:函数

    官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...

  3. Bash脚本编程学习笔记05:用户交互与脚本调试

    用户交互 在<学习笔记04>中我们有提到位置参数,位置参数是用来向脚本传递参数的一种方式.还有一种方式,是read命令. [root@c7-server ~]# read name alo ...

  4. Bash脚本编程学习笔记04:测试命令test、状态返回值、位置参数和特殊变量

    我自己接触Linux主要是大学学习的Turbolinux --> 根据<鸟哥的Linux私房菜:基础篇>(第三版) --> 马哥的就业班课程.给我的感觉是这些课程对于bash的 ...

  5. bash脚本编程学习笔记(一)

    bash脚本语言,不同于C/C++是一种解释性语言.即在执行前不需要事先转变为可执行的二进制代码,而是每次执行时经解释器解释后执行.bash脚本语言是命令的堆砌,即按照实际需要,结合命令流程机制实现的 ...

  6. bash脚本编程学习笔记(二)

    1.脚本编程之函数 函数是实现结构化编程重要的思想,主要目的是实现代码重用 定义一个函数: function FUNCNAME { command //函数体 }   FUNCNAME(){ //函数 ...

  7. Go语言学习笔记十: 结构体

    Go语言学习笔记十: 结构体 Go语言的结构体语法和C语言类似.而结构体这个概念就类似高级语言Java中的类. 结构体定义 结构体有两个关键字type和struct,中间夹着一个结构体名称.大括号里面 ...

  8. matlab学习笔记12_3串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields

    一起来学matlab-matlab学习笔记12 12_3 结构体 串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields 觉得有用的话 ...

  9. Swift 学习笔记 (类和结构体)

    类和结构体是一种多功能且灵活的构造体.通过使用与现存常量 变量 函数完全相同的语法来在类和结构体中定义属性和方法以添加功能. Swift中不需要你为自定义的类和结构体创建独立的结构和实现文件.在Swi ...

随机推荐

  1. volatile梳理

    volatile 可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值. 在Java中为了加快程序的运行 ...

  2. 当vps服务器被墙,如果用xshell连接

    当然你的被墙了,肯定是访问不了,你得去找一个新的可用的节点去访问,在xshell里面设置代理就能连接上.上图. 然后是两个不同的结点 鼠标放在小火箭上面就能显示

  3. CTF--HTTP服务--命令执行

    开门见山 1. 扫描靶机ip,发现PCS 172.18.5.1 2. 用nmap扫描靶机开放服务和服务版本 3. 再扫描靶机全部信息 4. 用nikto工具扫描http服务的敏感信息 5. 打开浏览器 ...

  4. window nginx 中文路径, 文件名乱码问题解决

    window nginx 中文路径, 文件名乱码, error, not found 此问题是由于windows系统编码与nginx编码设置不一致导致的,因此我们要统一二者的编码 nginx编码设置 ...

  5. 事务管理(ACID)

    目录 一.事务管理(ACID) 原子性(Atomicity) 一致性(Consistency) 持久性(Durability) 隔离性(Isolation) 二.事务隔离级别 脏读 不可复读 虚读(幻 ...

  6. nginx配置文件的通用语法介绍

    nginx的配置文件是ascii文本文件. 比如http{  }这种的是指令块,include  mime.types: 这种是指令,include是指令,mime.types指令的参数,指令和参数之 ...

  7. permission denied (publickey)问题的解决和向github添加ssh key

    使用ssh key这种方式进行clone ,pull github上面的项目,使用 git clone或者git pull origin master出现permission denied (publ ...

  8. 全局对象的构造函数会在main函数之前执行?

    #include <iostream> using namespace std; class CTest { public: CTest() { cout << "构 ...

  9. AI: 字体设计中的贝塞尔曲线

    http://www.xueui.cn/tutorials/illustrator-tutorials/designers-must-know-the-secret-of-the-bezier-cur ...

  10. ant编译solr源码生成eclipse项目,解决一直resolve,一直[ivy:retrieve]的问题

    这两天在学习solr,结果刚到编译solr源码就卡住了,足足卡了两天,网上找各种解决办法都是简单带过,说是缺少jar包,下载下来放到对应位置就好了....对应位置???咋不说这个问题用相应方法解决即可 ...