Linux Shell 编程从入门到实践3
10. while 循环和 until 循环的应用实践
循环语句是 Shell 脚本中实现重复执行指令的核心语法,常用于自动化运维、守护进程开发、数据计算等场景。Shell 脚本支持的循环语句包括 while、until、for 及 select,其中 while 和 until 侧重 “条件驱动型” 循环,是实现持续运行程序的核心语法。
10.1 当型和直到型循环语法
10.1.1 while 循环语句
while 循环属于 “当型循环”,满足条件时执行循环体,核心逻辑是 “先判断,后执行”。
基本语法:
while <条件表达式>
do
指令...
done
执行逻辑:
- 执行 while 后紧跟的条件表达式判断;
- 若条件成立(返回值 0),执行 do 与 done 之间的指令;
- 执行到 done 时,重新回到条件表达式判断,直至条件不成立(返回值非 0)时退出循环;
- 若初始条件不成立,循环体一次都不会执行。
提示:建议循环体内指令保持 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~49 的随机数并存储;
- 循环接收用户输入,校验输入合法性;
- 记录猜测次数,提示 “猜大 / 猜小 / 猜对”;
- 猜对后删除临时文件并退出。
[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): 1
第 1 次猜测:猜小了.
请输入数字(1-50): 30
第 2 次猜测:猜大了.
请输入数字(1-50): 27
第 3 次猜测:你太棒了!
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
注意:
{ comand1;command2; ...; } &可将多个命令组合后放入后台运行;- {} 内部两侧必须有空格,最后一个命令后需加分号。
并发控制示例
示例 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 本章小结
- while/until 循环核心场景:
- while 擅长实现守护进程、无限循环(需配合 sleep 控制频率),适合 < 1 分钟的高频循环;
- until 与 while 逻辑相反,适合 “直到某个条件满足才停止” 的场景;
- 多数普通循环场景可被 for 循环 + crontab 替代。
- 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): 10
第 1 次猜测:猜小了.
请输入数字(1-50): 30
第 2 次猜测:猜大了.
请输入数字(1-50): 20
第 3 次猜测:猜小了.
请输入数字(1-50): abc
输入无效!请输入 1-50 之间的数字!
请输入数字(1-50): 124
超出范围!请输入 1-50 之间的数字!
请输入数字(1-50): 22
第 4 次猜测:你太棒了!
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 个条目。
更多推荐



所有评论(0)