简介
在 shell 脚本中,循环结构用于重复执行一组代码块,包括 for 循环、while 循环,可以用于遍历数字、字符串、数组、文件等。这篇文章会详细介绍这两种遍历方式,以及各种实例场景。
文章目录结构如下
1. 循环遍历的特点
2. 循环的方式
2.1. for循环
① 遍历整数
② 遍历数组
③ 遍历字符串
④ 遍历命令
⑤ 无限循环
⑥ 单行写法
2.2. while循环
① 基础用法
② 实例用法
2.3. 跳出循环
① continue 跳出当前循环
② break 跳出整个循环
③ 跳出多嵌套循环
3. 实际应用场景
3.1. while 按行读取内容
3.2. while 交互读取用户输入的变量
3.3. while 输出倒计时
3.4. for 输出进度条
1. 循环遍历的特点
刚开始接触编程的同学能理解什么是循环,但对 "遍历" 这个词可能有些陌生。遍历就是逐个访问集合中的每个元素,或逐个执行某个操作。那么循环遍历通俗来说就是:将多个数据循环读取出来,再对这些被读取出来的数据做各种操作。
那么循环遍历方法可以用在什么地方呢?举个例子:
后端需要一部分数据导入数据库,这些数据需要人工构造。
先使用 echo 来构造一行符合需求的数据
echo "1,zhang_san,18,man"
如果数据库需要3行数据,我们还是可以使用 echo
echo "1,zhang_san,18,boy"
echo "2,li_si,20,boy"
echo "3,wang_wu,16,girl"
如果需要1w行、10w行呢,还能使用手动敲10w行代码吗,这显然很耗费人力,所以循环的作用就体现出来了。代码示例
# 循环10w次
for i in {1..100000};do
echo "${i},zhang_san,18,boy"
done
3行代码就能实现10w 行字符,是不是很方便呢。当然了,实际需求是很复杂的,但仍然可以使用循环方式解决。下面就带大家由浅到深慢慢学习。
2. 循环的方式
在 shell 中常见的循环方式有 2 种,它们的作用分别是:
- for 循环:按次数循环某些字符串、数组、文件等。
- while 循环:按条件循环,当条件为 True 即循环,条件为 False 则不循环。
这两种方式分别作用到不同的地方,下面就一起来学习吧!
2.1. for循环
① 遍历整数
遍历整数是最常见的方法,其中有2种写法,分别来看看他们怎么用吧
【写法一】按固定整数循环
for i in {1..5};do
echo "循环第${i}次"
done
- for in 是固定语法,不能修改。
- {1..5} 花括号是固定的,但里面的值可以修改 {开始值..结束值}。
- i 不是固定的,可以随意修改,它的作用是读取花括号中的值。
- do 是固定语法,不能修改,它表示循环开始。
- 中间代码 echo 是自定义的,可以写其他代码。
- done 是固定语法,不能修改,它表示循环结束。
我们来看看执行结果:
总共循环了 5 次,这 5 次是从花括号 {1..5} 读取的。
那么我们希望循环 3~7 怎么写呢?直接修改花括号的值
for i in {3..7};do
echo "当前变量i的值是:${i}"
done
花括号 {3..7}:3表示开始,7表示结束
代码块中的 echo 命令是为了让我们了解循环的过程,来看一个简单的实例,向文件夹 ./tmp 下面创建 10 个文件。
for i in {1..10};do
touch ./tmp/file${i}.txt
done
创建了 file1 ~ 10 的文件,共10个。
【写法二】按判断循环
for ((i=1; i<=5; i+=1));do
echo "当前i的值是:${i}"
done
- for (()) 是固定语法,不能修改。
- 括号中的 i=1 表示 i 的开始值为 1。
- 括号中的 i<=5 表示只循环 i 小于等于 5,若大于5则退出循环。
- 括号中的 i+=1 表示每循环一次后 i 的值都 +1。
- do 是固定语法,不能修改,它表示循环开始。
- 中间代码 echo 是自定义的,可以写其他代码。
- done 是固定语法,不能修改,它表示循环结束。
执行结果与《写法一》差不多
《写法二》与《写法一》相比,有2个优势。
优势一、中间的数值可以跳跃,代码如下(i+=2)
for ((i=1; i<=10; i+=2));do
echo "当前i的值是:${i}"
done
设置 i+=2 表示每次 i 的值 +2,这样就可以循环奇数。
指定 +2 并不是固定的,在实际中还可以 +3、+4、+n 等。
优势二、可以读取变量
# 定义一个变量为5
var=5
# 读取变量的值做循环
for ((i=1; i<=var; i+=2));do
echo "当前i的值是:${i}"
done
使用变量不仅仅可以指定最大值,最小值和跳跃值也可以指定
min=1 max=10 jump=3 # 读取变量的值做循环 for ((i=min; i<=max; i+=jump));do echo "当前i的值是:${i}" done
② 遍历数组
由于数组中包含不同的字符串或数字,上述《目录 ①遍历整数》的语法将无法使用,所以我们需要换一种写法
# 定义一个数组
arr=('AAA' 123 'BBB' 789)
# 遍历数组
for i in ${arr[@]};do
echo "当前数组的值为:${i}"
done
语法还是和《目录 ① 遍历整数》差不多,需要注意的是:遍历全部数组需要将变量这样写
${变量[@]}
按元素遍历,数组的分隔符默认空格。
如果不需要变量数组中前2个元素,这样写 ${变量[@]:2}
# 定义一个数组
arr=('AAA' 123 'BBB' 789)
# 遍历数组
for i in ${arr[@]:2};do
echo "当前数组的值为:${i}"
done
- 只遍历前面3个元素:${变量[@]:0:3}
- 只遍历后面3个元素:${变量[@]:(-3)}
- 遍历中间 3~5 个元素:${变量[@]:2:3}
详细的数组用法见另一篇文章:https://blog.csdn.net/m0_61066945/article/details/135070671
除了指定数组的值,我们还可以通过命令向数组赋值,并使用 for 循环遍历
# 定义一个数组
arr=(`ls /tmp/`)
# 遍历数组
for i in ${arr[@]};do
echo "当前数组的值为:${i}"
done
③ 遍历字符串
遍历字符串的方法和遍历数组的方式差不多,只是变量不同
# 定义一个字符串变量
var="AAA BBB CCC"
# 循环遍历这个变量
for i in ${var};do
echo "当前变量的值为:${i}"
done
我们定义了一个变量 var,使用 for 循环将变量中的值读取出来。
注意:for 循环遍历变量时,in 后面的变量不能加引号,如果加引号就表示这是1个字符
for i in "${var}";do
echo "当前变量的值为:${i}"
done
加上引号后,for 会认为这是一个字符,所以值循环一次。
我们不加引号可以将它理解成这样(直接遍历字符串)
for i in AAA BBB CCC;do
echo "当前变量的值为:${i}"
done
还有一点需要注意,变量中默认分隔符是空格,这里列举了几种修改分隔符的写法:
IFS=$"," # 将分隔符指定为逗号
IFS=$"abc" # 将分隔符指定为abc
IFS=$",: \n" # 将分隔符指定为逗号、冒号、空格、换行
举个例子(将分隔符指定为逗号)
# 定义一个变量
var="AAA BBB,CCC"
OLD_IFS=${IFS} # 读取当前分隔符
IFS=$"," # 指定分隔符为逗号
# 循环遍历这个变量
for i in ${var};do
echo "当前变量的值为:${i}"
done
IFS=${OLD_IFS} # 将分隔符修改会原来的值
结果如下
分隔符指定为逗号后,空格就不再生效。如果需要同时使用空格和逗号为分隔符,即设置为:IFS=$" ," 引号中同时包含一个空格和逗号。
④ 遍历命令
上面3种方法主要遍历固定的字符,在实际的场景中遍历命令的方式也是非常普遍的。
【案例一】遍历某个目录下以 .txt 结尾的文件
# find查找文件,for循环遍历
for file in $(find ./tmp -type f -name '*.txt');do
echo "当前变量的值为:${file}"
done
- 语法与前面差不多,命令放在 $( ) 中即可。
【案例二】遍历某个命令输出的结果
# seq输出一些数字
for i in $(seq 2 5);do
echo "当前变量的值为:${i}"
done
⑤ 无限循环
无限循环的语法与上面不同,不需要读取啥,这样写
for ((;;));do
echo "这是一个无限循环"
done
注意:这个方法会消耗一个cpu的资源,慎用!无限循环中最好加上 sleep
for ((;;));do echo "这是一个无限循环" sleep 1 done
⑥ 单行写法
单行写法语法如下
for i in {1..5};do 代码块1; 代码块2; done
单行写法与上面标准写法差不多,就是将换行符改为分号。如果将单行理解为4个部分的话,那么就是:(注意使用分号分隔)
【for 语法】;【do 开始循环】;【代码块】;【done 结束循环】
【案例1】创建10个文件
for i in {1..10};do touch tmp/file${i}.txt ;done
【案例二】给数组赋值 1~100 的奇数
# 定义一个空数组
arr=()
# 给数组赋值
for ((i=1; i<=100; i+=2));do arr+=(${i}) ;done
# 输出这个数组的值
echo ${arr[@]}
2.2. while循环
① 基础用法
while 不同于 for,for 是去读取某个值,while 是判断。如果判断结果为 True 则循环,如果判断结果为 False 则退出循环。
【案例一】直接给 while 加 true 表示无限循环
while true;do
echo "我是一个while循环"
sleep 1
done
【案例二】直接给 while 加 false 无法循环
while false;do
echo "我是一个while循环"
sleep 1
done
【案例三】在 false 前面加 !表示非 false,同 true
while ! false;do
echo "我是一个while循环"
sleep 1
done
通过上面3个案例总结出一个结论:只要判断的结果是正常的就可以循环,判断的结果是异常的则不循环。我们来判断一下数学运算。
# 定义一个变量
w=0
# 循环判断这个变量,只循环小于等于10的值
while [ ${w} -le 10 ];do
echo "我是一个while循环, 当前w的值是:${w}"
# 每循环一次,w的值+1
(( w += 1 ))
done
除了判断某个变量,我们还可以判断命令是否正确
# 循环判断PID为4549的进程是否存在
while ps u -p 4549;do
sleep 1
done
当进程存在时,一直循环;进程不存在时,退出循环。
② 实例用法
【案例一】判断端口是否被占用
while netstat -anpt |grep 3306; do
echo "端口3306已被占用"
sleep 1
done
端口号未被占用后,则退出循环。
【案例二】判断文件是否存在
file='./tmp/file.txt'
while [ ! -f ${file} ]; do
echo "没有 ${file} 文件, 那么创建这个文件"
touch ${file}
done
文件存在后,则退出循环
【案例三】判断网络 192.118.168.254 是否通畅
while ! timeout 3 ping 192.118.168.254; do
echo "192.118.168.254 无法ping通"
done
如果 IP 没有 ping 通,那么会一直去 ping,直到能够 ping 通。
2.3. 跳出循环
在实际应用中,经常会出现循环到某个地方时,希望循环停止而不退出脚本,那么需要用到以下两个命令:
① continue 跳出当前循环
举个例子,当变量 i = 3 时跳出当前循环,i 等于其他值时正常运行
# 循环1~5
for i in {1..5};do
# 判断:如果i=3,跳出当前循环
if [ ${i} -eq 3 ];then
continue
fi
# 执行其他命令
echo "当前变量i的值是:${i}"
done
只跳出当前循环,后面的循环继续
② break 跳出整个循环
当变量 i = 3 时直接跳出整个循环
# 循环1~5
for i in {1..5};do
# 判断:如果i=3,跳出当前循环
if [ ${i} -eq 3 ];then
break
fi
# 执行其他命令
echo "当前变量i的值是:${i}"
done
跳出整个循环后,后面的 4、5 都不会循环
③ 跳出多嵌套循环
当遇到多层嵌套时,希望跳过某个循环时应该怎么指定呢?
我们先写一个3层嵌套的例子
for i in {1..2};do
for j in {11..12};do
for k in {21..22};do
echo "当前各个变量结果:i=${i}, j=${j}, k=${k}"
done
done
done
每层输出2次,3层嵌套为 2³=8 次
将每层嵌套都打印字符,方便后面理解
for i in {1..2};do
echo "第一层嵌套: ${i}"
for j in {11..12};do
echo "第二层嵌套: ${j}"
for k in {21..22};do
echo "第三层嵌套: ${k}"
done
done
done
【案例一】跳出当前层级的嵌套:break 1
for i in {1..2};do
echo "第一层嵌套: ${i}"
for j in {11..12};do
echo "第二层嵌套: ${j}"
for k in {21..22};do
# 跳出当前嵌套
break 1
echo "第三层嵌套: ${k}"
done
done
done
对当前 break 的位置来说,当前层级就是第 1 层,所以跳出第 1 层后,就只剩下外面 2 层循环了。
【案例二】跳出上一层级的嵌套:break 2
for i in {1..2};do
echo "第一层嵌套: ${i}"
for j in {11..12};do
echo "第二层嵌套: ${j}"
for k in {21..22};do
# 跳出上面一层嵌套
break 2
echo "第三层嵌套: ${k}"
done
done
done
对当前 break 的位置来说,当前层级就是第 1 层,上一层是第 2 层。所以在上一层执行到 break 时就会跳出,而第 2 层 break 上面的代码是可以正常执行的。
【案例三】跳出上上层级的嵌套:break 3
for i in {1..2};do
echo "第一层嵌套: ${i}"
for j in {11..12};do
echo "第二层嵌套: ${j}"
for k in {21..22};do
# 跳出上上层嵌套
break 3
echo "第三层嵌套: ${k}"
done
done
done
对当前 break 的位置来说,当前层级就是第 1 层,上上层是第 3 层。所以在上上层执行到 break 时就会跳出,而第 3 层 break 上面的代码是可以正常执行的。
总结
不论是 continue 还是 break,如果不指定跳出的嵌套层级,那么就是跳出当前的嵌套。如果指定层级,那么当前层算是第1层,往上一层算低2层 ,再往上一层算是第3层,以此类推。
3. 实际应用场景
3.1. while 按行读取内容
虽然 for 循环也可以做到按行读取内容,但是需要修改分隔符(挺麻烦的),使用 while 就简单多了。代码如下:
while read str; do # 通过read读取文件内容,赋值为变量str(str可以自定义)
echo "当前文件内容:${str}"
done < ./file.txt # 这里需要指定文件路径
3.2. while 交互读取用户输入的变量
# 定义一个普通数组
declare -a arr
while [ ${#arr[@]} -lt 2 ]; do # 判断数组的个数小于等于2
read -p "请输入一个整数: " input # 交互读取变量
arr+=(${input}) # 将变量追加到数组
done
# 计算数组
result=$(( ${arr[0]} + ${arr[1]} ))
echo "${arr[0]} + ${arr[1]} = ${result}"
3.3. while 输出倒计时
# 设置倒计时初始值
count=10
# 循环实现数字每秒自动变化
while [ ${count} -ge 0 ];do
printf "\r倒计时: %2d" $count
sleep 1
(( count-- ))
done
echo -e "\n========== 结束 =========="
结果为动态,只能截几张静态图
3.4. for 输出进度条
# 设置总进度
total=100
# 循环输出进度条和百分比
for ((i=1; i<=total; i++)); do
# 计算百分比
percent=$((i * 100 / total))
# 输出进度条和百分比
printf "\r当前进度:%-$((total+1))s%2d%%" "$(perl -E "say '=' x ${i}")" $percent
# 休眠0.1秒
sleep 0.1
done; echo