3-shell-循环控制

38 阅读6分钟

Linux Shell 脚本

编程逻辑与控制结构

  • 编程逻辑处理

    • 顺序执行

    • 选择执行

    • 循环执行

      • 将某代码段重复运行多次
      • 重复运行多少次
      • 有进入条件和退出条件
  • shell中控制流结构

    • if ... then ... else ... fi

    • case 语句 ;; esac

    • for 循环

    • until 循环

    • while 循环

      • break 控制
      • continue 控制

if ... then ... else ... fi

  • 示例

    # 用中文描述,  如果if 1大于2, 那就打印echo 对, 否则就else 打印 不对的; fi结束
    if 条件; then 语句; else 语句; fi 
    ​
    [root@localhost opt]# if [[ 1 > 2 ]]; then echo "对"; else echo "不对"; fi 
    不对
    ​
    # 也可以用这种方式 Shell 条件逻辑 
    [root@localhost opt]# [[ 222 > 333 ]] && echo "对" || echo "不对"
    不对
    

case

  • 语法

    # case 变量名 in   从上下下看每个 变量名 做一下比较,如果匹配就执行这个逻辑
    case "$变量名" in 
      "模式1") 
        # 当变量值匹配“模式1”时执行的命令 
        命令1 
        命令2 
        ;;  # 结束当前分支(必须用两个分号) 
      "模式2" | "模式3")  # 用 | 表示“模式2或模式3都匹配” 
        # 当变量值匹配模式2或模式3时执行的命令 
        命令3 
        ;; 
      *)  # 所有模式都不匹配时(默认情况,类似 else) 
        # 默认执行的命令 
        echo "没有匹配的选项" 
        ;; 
    esac  # 结束 case 语句(case 的反写) 
    
  • 示例

    [root@localhost opt]# cat -n case.sh 
         1  #!/usr/bin/env bash
         2  #
         3  case $1 in 
         4    get1)
         5      echo "第一" ;;
         6    get2)
         7      echo "第二" ;;
         8    *)
         9      echo "没有这个变量" ;;
        10  esac
    [root@localhost opt]# bash case.sh get
    没有这个变量
    [root@localhost opt]# bash case.sh get1
    第一
    

for

  • 语法: for 变量名 in 列表; do 循环体 ; done

    • 格式

      • 双小括号方法,即((…))格式,也可以用于算术运算

      • 双小括号方法也可以使bash Shell实现C语言风格的变量操作 i=10 ((i++))

      • for循环的特殊格式:

        for ((控制变量初始化;条件判断表达式;控制变量的修正表达式)); do
            循环体
        done
        
      • 控制变量初始化:仅在运行到循环代码段时执行一次

      • 控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断

  • 列表

    • 直接给出列表

      • 整数列表

        • {start..end} 如: for i in {1..10}
        • $(seq [start [step]] end) 如 for i in seq 1 10
        • for (( i=0;i<=num;i++))
      • 返回列表命令

        • $(COMMAND)
  • 示例

    • for列表的几种用法

      [root@localhost opt]# for i in {1..10};do echo $i;done
      ​
      # 从1 开始打印1-10
      [root@localhost opt]# for u in `seq 1 1 10`;do echo $u;done
      # 从2 开始打印1-10
      [root@localhost opt]# for u in `seq 2 1 10`;do echo $u;done
      # 从1 开始每次加2,  就是13579
      [root@localhost opt]# for u in `seq 1 2 10`;do echo $u;done
      ​
      [root@localhost opt]# for ((i=0;i<=10;i++ ));do echo $i;done
      
    • 添加10个用户user1-user10,密码为8位随机字符

      echo $RANDOM|/usr/bin/md5sum | /usr/bin/cut -c 2-9
      # 随机生成密码for i in {1..10};do
          pass=`echo $RANDOM|/usr/bin/md5sum | /usr/bin/cut -c 2-9`
          count=`cat /etc/passwd  | cut -f1 -d":" | grep "user1$" -c`
          if [ $count -eq 0 ];then
             useradd user$i
             echo $pass | passwd --stdin user$i &>/dev/null
             echo "user$i   password: $pass" >> ./user.txt
          else
             echo 'user$i 已存在' >> /.user.txt
          fi
      done
      
    • 编写脚本,提示输入正整数n的值,计算1+2+…+n的总和

      read -p "请输入整数: " num
      sum=0
      for i in `seq 1 1 $num`;do
         #sum=$[$sum+$i]
         sum=$(($sum+$i))
      done
      echo $sum
      
    • 计算100以内所有能被3整除的整数之和

      sum=0
      for i in {1..100};do
         [ $(($i%3)) -eq 0 ] && sum=$(($sum+$i))
      done
      echo $sum
      
    • 打印九九乘法表

      for i in `seq 1 9`;do
         for u in `seq 1 9`;do
            [ $u -le $i ]  && echo -e "$i*$u=$(($i*$u))\t\c"
         done
         echo ""
      done
      

until

是 Shell 脚本中与 while 互补的循环结构,当条件为假时执行循环,直到条件变为真。以下是核心用法和示例

  • 语法

    until [ 条件测试 ]; do 
        # 循环体命令 
    done 
    
    • 先检查条件,若条件为 (非零退出状态),则执行循环体。
    • 每次循环后重新检查条件,一旦条件为 (退出状态为 0),循环终止。
  • 示例

    • 等待文件生成

      until [ -f /tmp/ready.flag  ]; do  
          echo "等待文件生成..."  
          sleep 5  
      done  
      echo "文件已就绪!"  
      
      # 说明:每 5 秒检查一次文件 /tmp/ready.flag ,直到文件存在后退出循环。
      
    • 服务启动检测

      until systemctl is-active nginx >/dev/null 2>&1; do  
          echo "Nginx 未运行,尝试启动..."  
          systemctl start nginx  
          sleep 3  
      done  
      echo "Nginx 已启动!"  
      
      # 说明:循环尝试启动 Nginx,直到服务状态变为 active。
      
    • 超时控制

      timeout=10  
      count=0  
      until [ $count -ge $timeout ]; do  
          ping -c1 example.com  && break  
          echo "第 $count 次尝试..."  
          ((count++))  
          sleep 1  
      done  
      # 说明:10 秒内尝试 ping 服务器,成功则提前退出,超时后终止。
      
    • 用户输入验证

      until $ ]]; do  
          read -p "确认操作?(Y/N): " input  
      done  
      # 说明:强制用户输入 Y/N 或 y/n,否则重复提示。
      

while

  • 语法

    while [ 条件表达式 ]; do 
        # 循环体(命令序列) 
    done 
    
    • 每次循环前判断,为 true 则执行循环体,为 false 则退出循环
    • 条件可使用 test 命令([ ])、双括号 (( ))(算术判断)或命令返回值(0 为 true,非 0 为 false)。
  • 冒号说明

    冒号: 与true 语句不执行任何实际的处理动作,但可用于返回一个出口状态为0 的测试条件。这两个语句常用于While 循环结构的元限循环测试条件,我们在脚本中经常会见到这样的使用

    while :   # 这表示是一个无限循环的过程,所以使用的时候要特别注意,不要形成了死循环,所以一般会定义一个sleep 时间,可以实现秒级别的cron 任务,其语法格式为:
    
    # ; 分号允许在一行放多个命令
    while :;do echo 'yep'; sleep 3;done  # 实例了一个无限循环,每隔3秒打印一次yep
    
  • 示例

    • 使用 [ ] 判断(字符串/文件属性等)

      count=1 
      while [ $count -le 5 ]; do  # 当 count ≤ 5 时循环 
          echo "第 $count 次循环" 
          count=$((count + 1))   # 计数器自增(也可用 count=$[count+1]) 
      done 
      
    • 使用 (( )) 算术判断(更简洁的数字比较)

      i=3 
      while ((i > 0)); do  # 当 i > 0 时循环 
          echo "倒计时: $i" 
          ((i--))          # i 自减 1 
      done 
      echo "结束!" 
      
      倒计时: 3 
      倒计时: 2 
      倒计时: 1 
      结束! 
      
    • 求100以内所有正奇数之和

      o=0
      j=0
      i=0
      while [ $i -lt 101 ];do
         [ $(($i%2)) -eq 0 ] && o=$(($o+$i)) || j=$(($j+$i))
        i=$(($i+1))
      done
      
      echo "偶数: " $o
      echo "奇数: " $j
      
    • 遍历文件内容(逐行读取)

      # 读取 file.txt  内容,打印行号和内容 
      line_num=1 
      while read line; do 
          echo "第 $line_num 行: $line" 
          line_num=$((line_num + 1)) 
      done < file.txt   # 通过重定向输入文件 
      # 说明:< file.txt 将文件内容作为 read 命令的输入,每次循环读取一行到变量 line。
      
    • 遍历命令输出(管道传递)

      # 遍历当前目录下的 .txt 文件(每行一个文件名) 
      ls *.txt | while read filename; do 
          echo "找到文件: $filename" 
          # 可添加处理命令,如 cat $filename、mv $filename ./backup/ 等 
      done 
      
    • 判断主机是否在线

      # 编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少
      read -p "请输入网段" num
      net=`echo $num | cut  -d'.' -f1-3`
      up=0
      down=0
      while true;do
         for i in `seq 1 3`;do
            ip=$net.$i
            ping -c 1 -w 1 $ip &>/dev/null
            if [ $? -eq 0 ];then 
            	echo "$ip 在线" 
            	up=$(($up+1))
           else
            	echo "$ip 不在线" 
              down=$(($down+1))
           fi
         done
         echo "在线主机$up,  不在线主线$down"
      break
      done
      
    • 打印九九乘法表

      i=1
      while [ $i -lt 10 ];do
         for u in `seq 1 9`;do
            [ $u -lt $i ] && echo -e "$u*$i=$(($i*$u))\t\c"
         done
      echo ""
      i=$(($i+1))
      done
      
    • 查找ip

      #!/bin/bash  
      #
      while true;do
           # 读取用户输入
           read -p "请输入一个IPv4地址: " ip_address
      
           # 定义IPv4地址的正则表达式
           ipv4_regex='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$'
      
           # 使用正则表达式判断输入是否为IPv4地址
           [[ $ip_address =~ $ipv4_regex ]] && break || echo "输入的地址不是一个有效的IPv4地址, 请重新输入"
      done
      
      sed -i "/^.*[[:space:]]unicast_peer /{n;s/^(.*[[:space:]])([0-9].*)(#.*)/\1${ip_address}\1\3/}" test.tx
      
continue
  • 说明

    • 无限循环(条件始终为 true)
    • 需配合 break 手动退出循环,常用于菜单交互持续监听
    • continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为: 第1层
  • 示例

    • 示例1:打印1-10,但跳过偶数

      #!/bin/bash 
      i=1 
      while [ $i -le 10 ]; do 
          if [ $((i % 2)) -eq 0 ]; then  # 如果i是偶数 
              i=$((i + 1))  # 先自增i(避免死循环) 
              continue      # 跳过当前循环剩余部分,直接进入下一次循环 
          fi 
          echo "当前数字:$i"  # 只打印奇数 
          i=$((i + 1)) 
      done 
      
      # 打印 1,3,5,7,9
      # 当 i 为偶数时,continue 会跳过 echo 语句,直接进入下一次循环判断。
      
    • 读取文件内容,跳过空行和注释行(以 # 开头)

      # 文本 test.txt
      hello 
      # 这是注释行 
       
      world 
      # 另一个注释 
      linux 
      
      #!/bin/bash 
      while IFS= read -r line; do  # 逐行读取文件 
          # 跳过空行或注释行 
          if [ -z "$line" ] || [[ "$line" == #* ]]; then 
              continue  # 跳过当前行,读取下一行 
          fi 
          echo "有效内容:$line" 
      done < test.txt   # 从文件test.txt 读取输入 
      
      # IFS= read -r line 用于完整读取一行(包括空格)。
      # -z "$line" 判断空行,[[ "$line" == #* ]] 判断以 # 开头的行,满足其一则 continue 跳过。
      
    • 要求用户输入1-5之间的数字,否则重新输入

      #!/bin/bash 
      echo "请输入1-5之间的数字:" 
      while true; do  # 无限循环,直到输入有效 
          read -r num 
          # 判断是否为数字,且在1-5之间 
          if ! [[ "$num" =~ ^[0-9]+$ ]] || [ "$num" -lt 1 ] || [ "$num" -gt 5 ]; then 
              echo "输入无效!请重新输入(1-5):" 
              continue  # 跳过剩余部分,重新读取输入 
          fi 
          echo "你输入的有效数字是:$num" 
          break  # 输入有效,退出循环 
      done 
      
    • while true; do  # true 始终返回 0(条件为真) 
          echo "输入 'q' 退出循环" 
          read input 
          if [ "$input" = "q" ]; then 
              break  # 输入 q 时退出循环 
          fi 
          echo "你输入了: $input" 
      done 
      
break
  • 语法:用于立即终止当前循环并跳出循环体,通常配合条件判断使用

  • 示例

    • 循环打印数字,当数字等于 5 时终止循环。

      #!/bin/bash 
      i=1 
      while true; do  # 无限循环(条件恒为真) 
        echo "当前数字: $i" 
        if [ $i -eq 5 ]; then  # 当 i=5 时 
          break  # 跳出循环 
        fi 
        i=$((i + 1))  # i 自增 1 
      done 
      echo "循环结束" 
      
    • 循环读取用户输入,输入 q 时退出。

      #!/bin/bash 
      while true; do 
        read -p "请输入内容(输入 q 退出): " input 
        if [ "$input" = "q" ]; then  # 输入为 "q" 时 
          echo "收到退出指令,程序结束" 
          break  # 跳出循环 
        fi 
        echo "你输入的是: $input" 
      done 
      
    • 遍历目录下文件,找到 target.txt 时停止搜索。

      #!/bin/bash 
      file_list=("a.txt"  "b.txt"  "target.txt"  "c.txt")   # 模拟文件列表 
      index=0 
       
      while [ $index -lt ${#file_list[@]} ]; do  # 遍历数组 
        current_file=${file_list[$index]} 
        echo "正在检查: $current_file" 
        if [ "$current_file" = "target.txt"  ]; then  # 找到目标文件 
          echo "找到目标文件: $current_file,退出搜索" 
          break  # 跳出循环 
        fi 
        index=$((index + 1)) 
      done 
      
    • 逐行读取文件内容,遇到空行时停止读取。

      data.txt
      第一行 
      第二行 
       
      第四行  # 空行后的内容 
      
      #!/bin/bash 
      while IFS= read -r line; do  # 逐行读取文件 
        if [ -z "$line" ]; then  # 判断行是否为空(-z 表示字符串长度为 0) 
          echo "遇到空行,停止读取" 
          break  # 跳出循环 
        fi 
        echo "读取到: $line" 
      done < data.txt   # 从文件 data.txt  读取输入 
      
      ### 打印
      读取到: 第一行 
      读取到: 第二行 
      遇到空行,停止读取 
      
  • 总结

    • while true 可创建无限循环,需通过 break 手动控制退出。
    • break 仅跳出当前最内层循环(嵌套循环中需注意层级)。
    • 通常与 if 条件判断结合,实现“满足特定条件时终止循环”的逻辑。