简介

    在 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

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐