10. while 循环和 until 循环的应用实践

循环语句是 Shell 脚本中实现重复执行指令的核心语法,常用于自动化运维、守护进程开发、数据计算等场景。Shell 脚本支持的循环语句包括 while、until、for 及 select,其中 while 和 until 侧重 “条件驱动型” 循环,是实现持续运行程序的核心语法。

10.1 当型和直到型循环语法

10.1.1 while 循环语句

while 循环属于 “当型循环”,满足条件时执行循环体,核心逻辑是 “先判断,后执行”。

基本语法

while <条件表达式>
do
  指令...
done

执行逻辑

  1. 执行 while 后紧跟的条件表达式判断;
  2. 若条件成立(返回值 0),执行 do 与 done 之间的指令;
  3. 执行到 done 时,重新回到条件表达式判断,直至条件不成立(返回值非 0)时退出循环;
  4. 若初始条件不成立,循环体一次都不会执行。

提示:建议循环体内指令保持 2 个空格缩进,提升代码可读性。

10.1.2 until 循环语句

until 循环属于 “直到型循环”,与 while 逻辑相反,条件不成立时执行循环体,条件成立时终止循环。

基本语法

until <条件表达式>
do
  指令...
done

until 循环语句的用法与while 循环语句的用法类似,区别是until会在条件表达式不成立时,进入循环执行指令;条件表达式成立时,终止循环。

10.2 当型和直到型循环的基本范例

示例 1:竖向打印 54321

while 实现方式

[root@server bin 16:42:40]# vim test1.sh
#!/bin/bash
# 初始化计数器为5
i=5
# 当i大于0时执行循环
while ((i>0))
do
  # 打印当前i值
  echo $i
  # 计数器自减1
  ((i--))
done

[root@server bin 16:43:36]# bash test1.sh 
5
4
3
2
1

until 实现方式

[root@server bin 16:46:06]# vim test2.sh
#!/bin/bash
# 初始化计数器为5
i=5
# 直到i等于0时终止循环
until ((i==0))
do
  # 打印当前i值
  echo $i
  # 计数器自减1
  ((i--))
done
[root@server bin 16:46:25]# bash test2.sh 
5
4
3
2
1
**示例2:**计算1+2+3+…+99+100的和
[root@server bin 16:47:55]# vim test3.sh
#!/bin/bash
# 初始化计数器i为1,累加和sum为0
i=1
sum=0
# 当i小于等于100时执行累加
while ((i<=100))
do
  # 累加当前i值到sum(等价于 let sum=sum+i)
  ((sum+=i))
  # 计数器自增1(等价于 let i++)
  ((i++))
done
# 输出最终累加结果
echo "1+2+3+...+99+100=$sum"

[root@server bin 16:48:22]# bash test3.sh 
1+2+3+...+99+100=5050
示例 3:计算 5 的阶乘
[root@server bin 16:48:26]# vim test4.sh
#!/bin/bash
# 初始化计数器i为1,阶乘结果sum为1(乘法初始值不能为0)
i=1
sum=1
# 当i小于等于5时执行累乘
while ((i<=5))
do
  # 累乘当前i值到sum
  ((sum*=i))
  # 计数器自增1
  ((i++))
done
# 输出5的阶乘结果
echo "5的阶乘为:$sum"
[root@server bin 16:49:20]# bash test4.sh 
5的阶乘为:120
示例 4:猴子吃桃问题

问题描述

  • 猴子第一天摘下若干桃子,当即吃了一半,又多吃 1 个;
  • 第二天早上吃了剩下桃子的一半,又多吃 1 个;
  • 每天早上都吃前一天剩下的一半零 1 个;
  • 第 10 天早上仅剩 1 个桃子,求第一天摘的桃子总数。

解题方法1: while 循环9次

[root@server bin 16:49:26]# vim test5.sh
#!/bin/bash
# 第10天剩余桃子数
today=1
# 存储前一天桃子数的临时变量
lastday=0
# 迭代次数:第10天倒推到第1天需计算9次
i=1
while ((i<=9))
do
  # 逆向公式:前一天数量 = (当天数量 + 1) × 2
  lastday=$[(today+1)*2]
  # 将前一天数量赋值给当天,用于下一次迭代
  today=${lastday}
  # 迭代次数自增
  ((i++))
done
# 输出第一天摘的桃子总数
echo "猴子第一天摘的桃子数量是:$today。"
[root@server bin 16:50:31]# bash test5.sh 
猴子第一天摘的桃子数量是:1534。

解题方法 2:函数递归调用

[root@server bin 16:52:16]# vim test6.sh
#!/bin/bash
# 定义递归函数,参数为天数
function sum (){
  # 递归终止条件:第10天仅剩1个桃子
  if [[ $1 = 1 ]];then
    echo $1
  else
     # 递归公式:前一天数量 = (后一天数量 + 1) × 2
     echo $[ ($(sum $[$1 -1]) + 1)*2 ]
  fi
}
# 调用递归函数计算第10天对应的初始数量
echo "猴子第一天摘的桃子数量是:$(sum 10)。"
[root@server bin 16:52:35]# bash test6.sh 
猴子第一天摘的桃子数量是:1534。
示例 5:猜数字游戏(无限循环 + 输入校验)

脚本功能

  1. 生成 1~49 的随机数并存储;
  2. 循环接收用户输入,校验输入合法性;
  3. 记录猜测次数,提示 “猜大 / 猜小 / 猜对”;
  4. 猜对后删除临时文件并退出。
[root@server bin 16:52:48]# vim random.sh 
#!/bin/bash

# 生成1~49的随机数
guess_number=$(( RANDOM % 49 + 1 ))
# 将随机数写入临时文件
echo "${guess_number}" > /root/bin/guess

# 初始化猜测次数为0
guess_count=0

# 无限循环,直至猜对退出
while true
do
  # 提示用户输入数字
  read -p "请输入数字(1-50): " num

  # 校验:是否为纯数字
  if ! [[ $num =~ ^[0-9]+$ ]]; then
    echo "输入无效!请输入 1-50 之间的数字!"
    continue
  fi

  # 校验:是否在1~50范围内
  if [ $num -lt 1 ] || [ $num -gt 50 ]; then
    echo "超出范围!请输入 1-50 之间的数字!"
    continue
  fi

  # 合法输入,猜测次数+1
  ((guess_count++))
  
  # 判断猜测结果
  if [ $num -eq $guess_number ];then
    echo "第 ${guess_count} 次猜测:你太棒了!"
    exit
  elif [ $num -gt $guess_number ];then
    echo "第 ${guess_count} 次猜测:猜大了."
  else
    echo "第 ${guess_count} 次猜测:猜小了."
  fi
done
[root@server bin 16:55:42]# bash random.sh 
请输入数字(1-50): 123
超出范围!请输入 1-50 之间的数字!
请输入数字(1-50): abc
输入无效!请输入 1-50 之间的数字!
请输入数字(1-50): 11 次猜测:猜小了.
请输入数字(1-50): 302 次猜测:猜大了.
请输入数字(1-50): 273 次猜测:你太棒了!

10.3 Shell 脚本后台运行与并发控制

脚本后台运行基础用法
命令 / 操作 功能说明
sh script.sh & 将脚本放到后台执行(后台运行核心方式)
Ctrl+C 终止当前前台运行的脚本 / 任务
Ctrl+Z 暂停当前前台运行的脚本 / 任务
bg 将暂停的任务放到后台继续执行(bg=background)
fg 将后台任务调至前台执行(fg=foreground),如fg 2调第 2 个任务
jobs 查看当前会话中后台运行的任务列表(含任务编号)
kill %任务编号 关闭指定编号的后台任务(任务编号通过 jobs 获取)
示例 1:让所有 CPU 满负荷运行
[root@server bin 16:58:20]# vim cpu.sh

#!/bin/bash
# 获取CPU核心数
cpu_count=$(lscpu|grep '^CPU(s)'|awk '{print $2}')
# 初始化计数器
i=1
# 为每个CPU核心启动一个无限循环进程
while ((i<=${cpu_count}))
do
  {
   # 无限循环执行空运算,占用CPU资源
   while :
   do
     ((1+1))
   done
   } &  # 将子进程放入后台
 ((i++))
done
[root@server bin 16:58:52]# bash cpu.sh 
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND               
  9048 root      20   0  113280    384    192 R 100.0  0.0   2:47.79 bash                  
  9049 root      20   0  113280    384    192 R 100.0  0.0   2:47.79 bash                  
  9050 root      20   0  113280    384    192 R 100.0  0.0   2:47.78 bash                  
  9051 root      20   0  113280    384    192 R 100.0  0.0   2:47.78 bash                  
  9047 root      20   0  113280    384    192 R 100.0  0.0   2:47.77 bash                  

注意:

  1. { comand1;command2; ...; } & 可将多个命令组合后放入后台运行;
  2. {} 内部两侧必须有空格,最后一个命令后需加分号。
并发控制示例
示例 1:固定并发数消耗 CPU
[root@server bin 17:05:20]# vim cpu_load 
#!/bin/bash
# 无限循环占用CPU
while true
do
  ((1+1))
done

[root@server bin 17:05:41]# vim multi_cpu_load
#!/bin/bash
# 获取CPU核心数
cpu_count=$(lscpu | awk '/^CPU\(s\):/ { print $2}')
# 初始化计数器
i=1
# 限制仅启动2个并发进程
while [ $i -le 2 ]
do
  # 后台运行CPU消耗脚本
  bash /root/bin/cpu_load &
  ((i++))
done
[root@server bin 17:07:06]# bash multi_cpu_load 
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND               
  9101 root      20   0  113280   1204   1028 R 100.0  0.0   0:13.36 bash                  
  9102 root      20   0  113280   1208   1032 R 100.0  0.0   0:13.36 bash                   
示例 2:动态控制并发数
[root@server bin 17:07:13]# vim cpu_load 
#!/bin/bash
# 后台运行CPU消耗循环
while true
do
  ((1+1))
done &

# 获取后台进程PID,仅运行10秒后终止
pid=$!
sleep 10 && kill -9 $pid

[root@server bin 17:10:07]# vim multi_cpu_load 
#!/bin/bash
# 获取CPU核心数
cpu_count=$(lscpu | awk '/^CPU\(s\):/ { print $2}')
# 无限循环启动进程
while true
do
  # 启动CPU消耗脚本
  bash /root/bin/cpu_load &
  # 并发数检测循环:控制并发数不超过CPU核心数
  while true
  do
    # 获取当前后台任务数
    jobs=$(jobs -l |wc -l)
    # 若并发数≥CPU核心数,暂停3秒后重新检测
    if [ $jobs -ge $cpu_count ];then
      sleep 3
    else
      # 并发数未超限,退出检测循环,继续启动新进程
      break
    fi
  done
done

[root@server bin 17:11:34]# bash multi_cpu_load 
   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND               
  9201 root      20   0  113280    380    196 R 100.0  0.0   0:08.36 bash                  
  9213 root      20   0  113280    388    200 R 100.0  0.0   0:08.35 bash                  
  9232 root      20   0  113280    384    196 R 100.0  0.0   0:08.35 bash                  
  9243 root      20   0  113280    388    200 R 100.0  0.0   0:08.34 bash                 
wait 指令:等待后台任务全部完成
[root@server bin 17:14:56]# vim wait.sh

#!/bin/bash
# 清空临时文件
> /tmp/sleep
# 初始化计数器
i=1
# 启动10个后台睡眠任务
while [ $i -le 10 ]
do
  # 后台执行:睡眠i秒后记录到临时文件
  ( sleep $i && echo sleep $i >> /tmp/sleep )&
  ((i++))
done
# 等待所有后台任务完成后,再执行后续指令
wait
# 输出所有完成的任务记录
cat /tmp/sleep

[root@server bin 17:15:12]# bash wait.sh 

10.4 企业生产实战:while 循环典型场景

示例 1:实时监控系统负载(每隔 2 秒记录)

实时输出版

[root@server bin 17:16:47]# vim while1.sh
#!/bin/bash
# 无限循环监控系统负载
while true
do
  # 输出系统负载信息(uptime包含1/5/15分钟负载)
  uptime
  # 间隔2秒
  sleep 2
done

[root@server bin 17:17:26]# bash while1.sh 
 17:17:32 up  8:08,  2 users,  load average: 0.02, 0.80, 1.40
 17:17:34 up  8:08,  2 users,  load average: 0.02, 0.80, 1.40
 
 [root@server bin 17:17:35]# vim while2.sh 
#!/bin/bash
# 无限循环记录系统负载到日志
while true
do
  # 将负载信息追加到日志文件
  uptime >> /tmp/loadaverage.log
  # 间隔2秒
  sleep 2
done
# 后台运行脚本
[root@server bin 17:18:27]# bash while2.sh &
[1] 9411
[root@server bin 17:19:30]# tail -f /tmp/loadaverage.log 
 17:19:26 up  8:10,  2 users,  load average: 0.00, 0.54, 1.23
 17:19:28 up  8:10,  2 users,  load average: 0.00, 0.54, 1.23
 17:19:30 up  8:10,  2 users,  load average: 0.00, 0.54, 1.23
示例 2:守护进程监控 sshd 服务(异常自动重启)

while 实现版

[root@server bin 17:22:02]# vim sshd.sh
#!/bin/bash
# 无限循环监控sshd服务
while true
do 
  # 检查sshd服务状态,重定向输出到空
  systemctl is-active sshd.service &>/dev/null
  # 若服务未运行(返回值非0),重启服务
  if [ $? -ne 0 ];then 
    systemctl restart sshd.service  &>/dev/null
  fi
  # 间隔5秒检测一次
  sleep 5
done
[root@server bin 17:22:28]# bash sshd.sh 

until格式:

[root@server bin 17:23:04]# vim sshd2.sh
#!/bin/bash
# until false 等价于无限循环(条件永远不成立)
until false
do 
  # 检查sshd服务状态
  systemctl is-active sshd.service &>/dev/null
  # 服务未运行则重启
  if [ $? -ne 0 ];then 
    systemctl restart sshd.service  &>/dev/null
  fi
  # 间隔5秒检测一次
  sleep 5
done
[root@server bin 17:23:25]# bash sshd2.sh 
示例 3:网站可用性监控(每隔 3 秒检测)
[root@server bin 17:24:02]# vim check_url.sh
#!/bin/bash

# 校验参数:必须传入1个URL参数
if [ $# -ne 1 ];then
  echo "Usage: $0 url"
  exit 1
fi

# 赋值待检测的URL
url="$1"

# 无限循环检测URL可用性
while true
do
  # 使用curl检测URL:超时5秒,静默模式,输出重定向到空
  if curl -o /dev/null -s --connect-timeout 5 $url;then
    echo $url is ok.
  else
    echo $url is error.
  fi
  # 间隔3秒检测一次
  sleep 3
done

执行结果

[root@server bin 17:24:29]# bash check_url.sh 
Usage: check_url.sh url
[root@server bin 17:24:52]# bash check_url.sh www.baidu.com
www.baidu.com is ok.
www.baidu.com is ok.
示例 4:模拟手机短信发送平台(余额管理)

功能说明

  • 每条短信扣费 0.15 元;
  • 余额不足 0.15 元时禁止发送,提示充值;
  • 支持余额查询、短信发送、充值、退出功能。
[root@server ~ 19:20:40]# vim mobile.sh
#!/bin/bash

# 初始化余额(单位:元)
money=0.5

# 短信存储文件路径
msg_file=/tmp/message
# 清空历史短信记录
> $msg_file

# ========== 函数定义 ==========
# 打印操作菜单
function print_menu () {
  cat << EOF
1. 查询余额
2. 发送消息
3. 充值
4. 退出
EOF
}

# 校验输入是否为有效数字
function check_digit () {
  expr $1 + 1 &> /dev/null && return 0 || return 1
}

# 显示当前余额
function check_money_all () {
    echo "余额为:$money。"
}

# 检查余额是否足够发送短信(≥0.15元)
function check_money () {
  # 转换为分,避免小数运算误差
  new_money=$(echo "$money*100"|bc|cut -d . -f1)
  if [ ${new_money} -lt 15 ];then
    echo "余额不足,请充值。"
    # 返回1表示余额不足
    return 1
  else
    # 返回0表示余额充足
    return 0
  fi
}

# 充值功能
function chongzhi () {
  read -p "充值金额(单位:元):" chongzhi_money
  # 循环校验充值金额:必须为≥1的数字
  while true
  do
    check_digit $chongzhi_money
    if [ $? -eq 0 ] && [ ${chongzhi_money} -ge 1 ];then
      # 累加充值金额(保留2位小数)
      money=$( echo "($money+${chongzhi_money})"|bc)
      echo "当前余额为:$money"
      return 0
    else
      read -p "重新输入充值金额:" chongzhi_money 
    fi
  done
}

# 发送短信功能
function send_msg () {
  # 先检查余额是否充足
  check_money
  if [ $? -eq 0 ];then
    # 读取用户输入的短信内容
    read -p "msg: " message
    # 保存短信到文件
    echo "$message" >> ${msg_file}

    # 扣除短信费用(0.15元),转换为分运算
    new_money=$(echo "scale=2;($money*100-15)" | bc |cut -d. -f1 )
    # 处理小数前置0丢失问题(如0.05会被转为5)
    if [ ${new_money} -ge 100 ];then
      money=$(echo "scale=2;${new_money}/100" | bc )
    else
      money=0$(echo "scale=2;${new_money}/100" | bc )
    fi
    # 输出扣费后余额
    echo "当前余额为:$money"
  fi
}

# ========== 主程序 ==========
while true
do
  # 打印操作菜单
  print_menu
  echo
  # 读取用户选择
  read -p "请输入你的选择:" choice
  # 清屏(提升交互体验)
  clear
  # 根据选择执行对应功能
  case  $choice in
    1)
      check_money_all
      ;;
    2)
      send_msg
      ;;
    3)
      chongzhi
      ;;
    4)
      exit
      ;;
    *)
      echo "只能从1、2、3、4中选择。" 
      ;;
  esac
  echo
done

[root@server ~ 19:21:30]# bash mobile.sh 

余额为:10.35。

1. 查询余额
2. 发送消息
3. 充值
4. 退出

请输入你的选择:

msg: hello
当前余额为:10.20

1. 查询余额
2. 发送消息
3. 充值
4. 退出

请输入你的选择:
充值金额(单位:元):1
当前余额为:11.20

1. 查询余额
2. 发送消息
3. 充值
4. 退出

请输入你的选择:

10.5 while 循环按行读文件的方式总结

  • 方式1:采用exec读取文件,然后进入while 循环处理。
[root@server bin 19:24:38]# vim exec.sh
#!/bin/bash
# 将/etc/hosts作为标准输入
exec < /etc/hosts
# 逐行读取输入内容
while read line
do
  # 打印每行内容
  echo $line
done
[root@server bin 19:25:15]# bash exec.sh 
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
10.1.8.10 server.liu.cloud server
10.1.8.11 client.liu.cloud client

方式 2:cat + 管道读取

[root@server bin 19:25:22]# vim exec2.sh 
#!/bin/bash
# cat读取文件内容,通过管道传递给while
cat /etc/hosts | while read line
do
  echo $line
done
[root@server bin 19:26:30]# bash exec2.sh 
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
10.1.8.10 server.liu.cloud server
10.1.8.11 client.liu.cloud client

方式 3:done 处重定向读取

[root@server bin 19:26:36]# vim exec3.sh
#!/bin/bash
# 逐行读取文件内容
while read line
do
  echo $line
# done后指定读取的文件
done < /etc/hosts
[root@server bin 19:27:25]# bash exec3.sh 
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
10.1.8.10 server.liu.cloud server
10.1.8.11 client.liu.cloud client

方式 4:修改分隔符

[root@server bin 19:27:30]# vim exec4.sh
#!/bin/bash
# 将Shell分隔符改为换行符(默认是空格/制表符/换行)
IFS=$'\n'
# 遍历cat读取的文件内容(按行分割)
for line in $(cat /etc/hosts)
do
  echo $line
done
[root@server bin 19:28:50]# bash exec4.sh 
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
10.1.8.10 server.liu.cloud server
10.1.8.11 client.liu.cloud client

10.6 本章小结

  1. while/until 循环核心场景
    • while 擅长实现守护进程、无限循环(需配合 sleep 控制频率),适合 < 1 分钟的高频循环;
    • until 与 while 逻辑相反,适合 “直到某个条件满足才停止” 的场景;
    • 多数普通循环场景可被 for 循环 + crontab 替代。
  2. Shell 语句选型参考
语句类型 核心使用场景
条件表达式 简短条件判断(文件存在、字符串非空等)
if 语句 多值判断(值数量少)、复杂逻辑分支
for 语句 固定次数循环、批量处理(最常用)
while 语句 守护进程、无限循环、按行读文件
case 语句 服务启动脚本(固定字符串匹配)
select 语句 交互式菜单(极少用,可被 cat here 文档替代)
函数 简化逻辑、减少重复代码

11. for 和 select 循环语句的应用实践

for 循环是 Shell 中固定次数循环的核心语法,适用于已知循环次数的场景(如批量处理、数值计算);select 循环则专为交互式菜单设计,是实现终端交互的便捷方式。

11.1 for 循环语法结构

11.1.1 变量取值型(列表遍历)

基本语法

for 变量名 in 变量取值列表
do
  指令...
done

关键说明

  • in 变量取值列表 可省略,省略后等价于 in "$@"(遍历脚本传入的所有参数);
  • 变量取值列表以空格分隔,每次循环变量名取列表中的一个值;
  • 执行到 done 时,变量名自动取下一个值,直至列表遍历完毕。

11.1.2 C 语言型(数值循环)

基本语法

for ((expl;exp2;exp3))
do
  指令...
done

表达式说明

  • 表达式 1:变量初始化(如i=0);
  • 表达式 2:循环条件(如i<100);
  • 表达式 3:变量自增 / 自减(如i++);
  • 语法与 C 语言 for 循环一致,结构更规范,适合数值范围循环。

11.2 for 循环语句的基础实践

示例 1:竖向升序打印 1~5

列表型实现

[root@server bin 19:30:33]# vim for1.sh
#!/bin/bash
# 遍历1~5的数字列表
for i in {1..5}
do
  echo $i
done
[root@server bin 19:30:56]# bash for1.sh 
1
2
3
4
5

[root@server bin 19:31:05]# vim for2.sh
#!/bin/bash
# 通过seq生成1~5的序列
for i in $(seq 5)
do
  echo $i
done
[root@server bin 19:31:58]# bash for2.sh 
1
2
3
4
5

C 语言型实现

[root@server bin 19:32:11]# vim for3.sh
#!/bin/bash
# 初始化i=1,循环至i<=5,每次i+1
for ((i=1;i<=5;i++))
do
  echo $i
done
[root@server bin 19:33:00]# bash for3.sh 
1
2
3
4
5

while 对比实现

[root@server bin 19:33:06]# vim while1.sh
#!/bin/bash
i=1
while ((i<=5)) 
do
  echo $i
  ((i++))
done
[root@server bin 19:34:10]# bash while1.sh 
1
2
3
4
5
示例 2:竖向降序打印 5~1
[root@server bin 19:34:16]# vim for4.sh
#!/bin/bash
# 遍历5~1的数字列表
for i in {5..1}
do
  echo $i
done
[root@server bin 19:35:15]# bash for4.sh 
5
4
3
2
1

[root@server bin 19:35:30]# vim for5.sh
#!/bin/bash
# seq 起始 步长 结束:生成5到1,步长-1
for i in $(seq 5 -1 1)
do
  echo $i
done
[root@server bin 19:36:02]# bash for5.sh 
5
4
3
2
1
示例 3:求和、求乘积
[root@server bin 19:36:47]# vim for_sum_1.sh
#!/bin/bash
# 初始化累加和为0
sum=0
# 遍历1~10的数字
for num in {1..10}
do
  # 累加当前数字到sum
  let sum+=num
done
# 输出结果
echo  sum=$sum
[root@server bin 19:37:20]# bash for_sum_1.sh 
sum=55
[root@server bin 19:37:28]# vim for_sum_2.sh
#!/bin/bash
sum=0
# C语言型for循环
for (( num=1;num<=10;num++ ))
do
  let sum+=num
done
echo sum=$sum
[root@server bin 19:38:16]# bash for_sum_2.sh 
sum=55
示例 4:求水仙花数(3 位数)

定义:水仙花数是指 3 位数中,每个位上数字的 3 次幂之和等于其本身(如 153=1³+5³+3³)。

[root@server bin 19:38:23]# vim water_god_flower.sh 
#!/bin/bash
# 遍历所有3位数
for num in {100..999}
do
   # 提取个位
   ge=$[num%10]
   # 提取十位
   shi=$[num%100/10]
   # 提取百位
   bai=$[num/100]
   # 计算各位数字的3次幂之和
   temp_num=$[ ge**3 + shi**3 + bai**3 ]
   # 判断是否为水仙花数
   if ((temp_num==num));then
        # 不换行输出
        echo -n "$num "
   fi
done
[root@server bin 19:39:14]# bash water_god_flower.sh 
153 370 371 407 
示例 5:求 100~200 之间的素数

定义:素数(质数)是指除了 1 和自身外,不能被任何整数整除的数。

[root@server bin 19:39:52]# vim sushu.sh
#!/bin/bash

echo -n "100~200之间所有的素数为:"
# 遍历100~200的数字
for ((i=100;i<=200;i++))
do
	# 内层循环:检查2~i-1是否能整除i
	for ((j=2;j<=i-1;j++))
	do
		# 若能整除,说明不是素数,跳出内层循环
		if [ $[i%j] -eq 0 ];then
			break
		fi
		# 若循环到i-1仍未整除,说明是素数
		if [ $j -eq $[i-1] ];then
			echo -n " $i"
		fi
	done
done
echo

执行结果

[root@server bin 19:40:18]# bash sushu.sh 
100~200之间所有的素数为: 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
示例 6:打印九九乘法表
[root@server bin 19:40:23]# vim 99.sh 
#!/bin/bash
# 外层循环:控制行数(1~9)
for ((num1=1;num1<=9;num1++))
do
    # 内层循环:控制每行的列数(1~当前行数)
    for ((num2=1;num2<=num1;num2++))
    do
        # 格式化输出:不换行,制表符分隔
        echo -en "${num2}x${num1}=$((num2*num1))\t"
    done
    # 每行结束后换行
    echo
done
[root@server bin 19:41:59]# bash 99.sh 
1x1=1	
1x2=2	2x2=4	
1x3=3	2x3=6	3x3=9	
1x4=4	2x4=8	3x4=12	4x4=16	
1x5=5	2x5=10	3x5=15	4x5=20	5x5=25	
1x6=6	2x6=12	3x6=18	4x6=24	5x6=30	6x6=36	
1x7=7	2x7=14	3x7=21	4x7=28	5x7=35	6x7=42	7x7=49	
1x8=8	2x8=16	3x8=24	4x8=32	5x8=40	6x8=48	7x8=56	8x8=64	
1x9=9	2x9=18	3x9=27	4x9=36	5x9=45	6x9=54	7x9=63	8x9=72	9x9=81	
示例7:求出满足条件的4位数

在这里插入图片描述

解题思路:

在这里插入图片描述

  • 余数为 5,除数≥6;
  • 商的每一位都是 1(因除数≥6,商 × 除数仍为个位数)。

思路 1:枚举所有 4 位数

[root@server bin 19:45:05]# vim compute1.sh 
#!/bin/bash
# 遍历所有4位数
for num in {1000..9999}
do
  # 遍历除数(6~9)
  for i in {6..9}
  do
    # 提取4位数的每一位
    num1=$[$num/1000]    # 千位
    num2=$[$num/100%10]  # 百位
    num3=$[$num/10%10]   # 十位
    num4=$[$num%10]      # 个位
    
    # 计算第一步余数
    e=$[num1*10+num2-i]
    # 余数必须是个位数
    if ((e<10));then
      # 计算第二步余数
      f=$[e*10+num3-i]
      # 第二步余数为个位数,且第三步余数为5
      if ((f<10 && $[f*10+num4-i]==5));then
        echo "${i}x111+5=$num"
      fi
    fi
  done
done
[root@server bin 19:45:39]# bash compute1.sh 
9x111+5=1004

思路 2:逐位枚举

[root@server bin 19:45:43]# vim compute2.sh 
#!/bin/bash
# 遍历除数(6~9)
for ((i=6;i<=9;i++))
do
  # 遍历千位(1~9)
  for ((a=1;a<=9;a++))
  do
    # 遍历百位(0~9)
    for ((b=0;b<=9;b++))
    do
      # 计算第一步余数
      e=$[a*10+b-i] 
      # 余数为个位数
      if [ $e -lt 10 ];then
        # 遍历十位(0~9)
        for ((c=0;c<=9;c++))
        do
          # 计算第二步余数
          f=$[e*10+c-i]
          # 余数为个位数
          if [ $f -lt 10 ];then
            # 遍历个位(0~9)
            for ((d=0;d<=9;d++))
            do
              # 第三步余数为5
              if [ $[f*10+d-i] -eq 5 ];then
                echo "${i}x111+5=$[i*111+5]"
              fi
            done
          fi
        done
      fi
    done
  done
done

执行效率对比

[root@server bin 19:47:01]# time bash compute1.sh
9x111+5=1004

real	0m0.316s
user	0m0.302s
sys	0m0.014s


[root@server bin 19:47:34]# time bash compute2.sh
9x111+5=1004

real	0m0.007s
user	0m0.007s
sys	0m0.001s
示例 8:百钱买百鸡问题

问题描述(张丘建《算经》):

  • 鸡翁 1 只值 5 钱,鸡母 1 只值 3 钱,鸡雏 3 只值 1 钱;
  • 用 100 钱买 100 只鸡,求鸡翁、鸡母、鸡雏的数量组合。
[root@server bin 19:48:17]# vim chicken.sh 
#!/bin/bash
# 定义总数量和总钱数
sum=100
money=100

# 遍历鸡翁数量(最多20只,5×20=100)
for ((cock_num=1;cock_num<=money/5;cock_num++))
do
  # 遍历鸡母数量(最多33只,3×33=99)
  for ((hen_num=1;hen_num<=money/3;hen_num++))
  do
    # 遍历鸡雏数量(必须是3的倍数)
    for chick_num in {3..100..3}
    do
      # 条件:总数量=100 且 总钱数=100
      if [ $[ cock_num+hen_num+chick_num ] -eq $sum -a $[ cock_num*5+hen_num*3+chick_num/3 ] -eq $money ];then
        echo "鸡翁:$cock_num  鸡母:$hen_num  鸡雏:$chick_num"
        echo "$cock_num*5+$hen_num*3+$chick_num/3=100"
        echo
      fi
    done
  done
done

执行结果:

[root@server bin 19:49:47]# bash chicken.sh 
鸡翁:4  鸡母:18  鸡雏:78
4*5+18*3+78/3=100

鸡翁:8  鸡母:11  鸡雏:81
8*5+11*3+81/3=100

鸡翁:12  鸡母:4  鸡雏:84
12*5+4*3+84/3=100
示例 9:三对情侣婚礼配对问题

问题描述

  • 新郎:A、B、C;新娘:X、Y、Z;
  • A 说娶 X,X 说嫁 C,C 说娶 Z;三人全说谎;
  • 求真实的配对关系。

方法 1:直接枚举字符

[root@server bin 19:49:53]# vim wedding1.sh
#!/bin/bash
# 枚举A的配对对象
for A in X Y Z
do
  # 枚举B的配对对象
  for B in X Y Z
  do
    # 枚举C的配对对象
    for C in X Y Z
    do
      # 条件:1. 三人配对不重复;2. 所有谎言的反向条件
      if [ "$A" != "$B" -a "$A" != "$C" -a "$B" != "$C" -a "$A" != "X" -a "$C" != "X" -a "$C" != "Z" ];then
        echo A 与 $A 结婚
        echo B 与 $B 结婚
        echo C 与 $C 结婚
      fi
    done
  done
done
[root@server bin 19:51:04]# bash wedding1.sh 
A 与 Z 结婚
B 与 X 结婚
C 与 Y 结婚

方法 2:数字映射字符(更通用)

[root@server bin 19:51:08]# vim wedding2.sh
#!/bin/bash
# 定义映射函数:数字→新娘(1=X,2=Y,3=Z)
duixiang ()
{
  case $1 in
    1)
     echo X
     ;;
    2)
     echo Y
     ;;
    3)
     echo Z
     ;;
  esac
}

# 枚举A的配对(1=X,2=Y,3=Z)
for ((A=1;A<=3;A++))
do
  # 枚举B的配对
  for ((B=1;B<=3;B++))
  do
    # 枚举C的配对
    for ((C=1;C<=3;C++))
    do
      # 条件:配对不重复 + 谎言反向条件
	  if [ $A -ne 1 -a $C -ne 1 -a $C -ne 3 -a $A -ne $B -a $A -ne $C -a $B -ne $C ];then
	    echo "A 与 $(duixiang $A) 结婚"
	    echo "B 与 $(duixiang $B) 结婚"
	    echo "C 与 $(duixiang $C) 结婚"
	    exit
      fi
    done
  done
done
[root@server bin 19:52:06]# bash wedding2.sh 
A 与 Z 结婚
B 与 X 结婚
C 与 Y 结婚

11.3 for 循环的企业级案例

示例 1:批量修改文件扩展名(txt→jpg)
# 创建测试文件
[root@server bin 16:11:53]# touch file-{1..3}.txt
[root@server bin 16:13:40]# ls file-{1..3}.txt
file-1.txt  file-2.txt  file-3.txt

# 编写批量重命名脚本
[root@server bin 16:13:59]# vim change_name.sh

#!/bin/bash
# 遍历所有txt文件
for file in $(ls *.txt)
do
  # 替换扩展名:txt→jpg
  new_name=$(echo $file|sed 's/.txt$/.jpg/')
  # 重命名文件
  mv $file ${new_name}
done

# 执行脚本
[root@server bin 16:23:16]# bash change_name.sh 
# 验证结果
[root@server bin 16:23:24]# ls file*
file-1.jpg  file-2.jpg  file-3.jpg

11.4 随机数应用

[root@server bin 19:57:50]# vim random.sh 
#!/bin/bash

# 生成1~50的随机数
guess_number=$(( RANDOM % 50 + 1 ))
# 保存随机数到临时文件
echo "${guess_number}" > /root/bin/guess

# 初始化猜测次数
guess_count=0

# 无限循环直至猜对
while true
do
  read -p "请输入数字(1-50): " num

  # 校验:是否为纯数字
  if ! [[ $num =~ ^[0-9]+$ ]]; then
    echo "输入无效!请输入 1-50 之间的数字!"
    continue
  fi

  # 校验:是否在1~50范围内
  if [ $num -lt 1 ] || [ $num -gt 50 ]; then
    echo "超出范围!请输入 1-50 之间的数字!"
    continue
  fi

  # 合法输入,猜测次数+1
  ((guess_count++))

  # 判断猜测结果
  if [ $num -eq $guess_number ];then
    echo "第 ${guess_count} 次猜测:你太棒了!"
    exit
  elif [ $num -gt $guess_number ];then
  echo "第 ${guess_count} 次猜测:猜大了."
  else
    echo "第 ${guess_count} 次猜测:猜小了."
  fi
done
[root@server bin 19:59:46]# bash random.sh 
请输入数字(1-50): 101 次猜测:猜小了.
请输入数字(1-50): 302 次猜测:猜大了.
请输入数字(1-50): 203 次猜测:猜小了.
请输入数字(1-50): abc
输入无效!请输入 1-50 之间的数字!
请输入数字(1-50): 124
超出范围!请输入 1-50 之间的数字!
请输入数字(1-50): 224 次猜测:你太棒了!

11.5 select 循环语句(交互式菜单)

11.5.1 基本语法

select 变量名 [ in 菜单取值列表 ]
do
  指令...
done

11.5.2 核心特性

  • 自动生成数字序号的菜单,默认提示符为#?

  • 用户只能输入序号,输入后变量名对应列表中的值;

  • 无终止循环,需手动执行 exit 退出;

  • 环境变量:

    • PS3:自定义选择提示符;
    • REPLY:存储用户输入的序号。

11.5.3 实战案例

示例 1:基础菜单展示
[root@server bin 20:01:48]# vim select1.sh
#!/bin/bash
# 生成课程选择菜单
select course in mysql python linux
do
  # 输出用户选择的课程
  echo $course
done

[root@server bin 20:02:54]# bash select1.sh 
1) mysql
2) python
3) linux
#? 1
mysql
#? 2
python
#? 3
linux
示例 2:自定义提示符 + case 分支
[root@server bin 20:03:18]# vim select2.sh 
#!/bin/bash
# 自定义选择提示符
PS3="选择你的课程: "
# 生成菜单(含退出选项)
select course in linux mysql python exit
do
  # 输出用户选择的序号
  echo "你选择了第 $REPLY 个条目。"
  # 根据选择执行对应操作
  case $course in
    linux)
      echo linux xxx
      ;;
    mysql)
      echo mysql yyy
      ;;
    python)
      echo python zzz
      ;;
    exit)
      exit
      ;;
  esac
  echo
done

# 执行结果
[root@server bin 20:05:10]# bash select2.sh 
1) linux
2) mysql
3) python
4) exit
选择你的课程: 1
你选择了第 1 个条目。
linux xxx

选择你的课程: 2
你选择了第 2 个条目。
mysql yyy

选择你的课程: 3
你选择了第 3 个条目。
python zzz

选择你的课程: 4
你选择了第 4 个条目。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐