【网络运维】Shell 脚本编程: for 循环与 select 循环
Shell 脚本编程: for 循环与 select 循环
循环语句命令常用于重复执行一条指令或一组指令,直到条件不再满足时停止,Shell脚本语言的循环语句常见的有while、until、for及select循环语句。
本文将详细介绍Shell编程中for循环和select循环的各种应用场景和实践技巧。
for 循环语句介绍
for 循环是 Shell 编程中常用的循环结构,主要用于执行次数有限的循环任务。与 while 循环不同,for 循环更适合处理已知迭代次数的场景。for 循环主要有两种语法结构:变量取值型和C语言型。
变量取值型 for 循环
语法结构:
for 变量名 in 变量取值列表
do指令...
done
说明:
in 变量取值列表
可以省略,省略时相当于in "$@"
- 执行过程:变量名依次获取取值列表中的每个值,执行循环体内的指令
执行流程:
- 变量获取列表中的第一个值
- 执行循环体内的指令
- 执行到 done 后结束本次循环
- 变量获取列表中的下一个值,重复上述过程
- 直到取完列表中所有值
C 语言型 for 循环
语法结构:
for ((exp1; exp2; exp3))
do指令...
done
说明:
- exp1:变量初始化(如:i=0)
- exp2:循环条件(如:i<100)
- exp3:变量更新(如:i++)
执行流程:
- 执行初始化表达式
- 检查条件表达式,如果为真则进入循环
- 执行循环体内的指令
- 执行变量更新表达式
- 重复步骤2-4,直到条件表达式为假
for 循环基础实践示例
示例1:竖向升序打印数字
#!/bin/bash
# 列表型for循环
for i in {1..5}
doecho $i
done# 使用seq命令
for i in $(seq 5)
doecho $i
done# C语言型for循环
for ((i=1; i<=5; i++))
doecho $i
done# while循环实现相同功能
i=1
while ((i<=5))
doecho $i((i++))
done
示例2:竖向降序打印数字
#!/bin/bash
# 使用序列表达式
for i in {5..1}
doecho $i
done# 使用seq命令带步长
for i in $(seq 5 -1 1)
doecho $i
done
示例3:求和与求乘积
#!/bin/bash
# 求1-10的和
sum=0
for i in {1..10}
dosum=$[ sum + i ] # 累加计算
done
echo $(seq -s '+' 10)=$sum # 显示计算过程# 求1-10的乘积
sum=1
for i in {1..10}
do((sum *= i)) # 累乘计算
done
echo $(seq -s '*' 10)=$sum # 显示计算过程
示例4:求水仙花数
水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身。
#!/bin/bash
for num in {100..999}
don1=$[num/100] # 获取百位数n2=$[num%100/10] # 获取十位数n3=$[num%10] # 获取个位数# 判断是否为水仙花数if [ $[ n1**3 + n2**3 + n3**3 ] -eq $num ];thenecho $n1^3 + $n2^3 + $n3^3 = $n1$n2$n3fi
done
示例5:求素数
素数又称质数,是指除了 1 和它本身以外,不能被任何整数整除的数。
#!/bin/bash
echo -n "100~200之间所有的素数为:"
for ((i=100; i<=200; i++))
do# 除数的范围是2和本身-1的数for ((j=2; j<=i-1; j++))do# 如果能整除,说明不是质数,跳出本次循环if [ $[i%j] -eq 0 ];thenbreakfi# 如果本次循环到最后都不能整除,则是质数if [ $j -eq $[i-1] ];thenecho -n " $i"fidone
done
echo
执行结果:
100~200之间所有的素数为: 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199
示例6:九九乘法表
#!/bin/bash
for num1 in {1..9}
dofor ((num2=1; num2<=num1; num2++))do# 格式化输出,使用制表符对齐echo -en "$num2*$num1=$[ num2 * num1 ]\t"doneecho # 换行
done
示例7:求出满足条件的4位数
解题思路:
- 余数是5,除数肯定大于5,至少是6
- 大于等于6的个位数乘以一个数,结果还是一位数,则商上面的每个数都是1
解法1:从1000开始枚举到9999
#!/bin/bash
# num是被除数
for num in {1000..9999}
do# i是除数for i in {6..9}do# 分解数字的各位num1=$[$num/1000] # 千位num2=$[$num/100%10] # 百位num3=$[$num/10%10] # 十位num4=$[$num%10] # 个位e=$[num1*10+num2-i] # 第一次计算if ((e<10));thenf=$[e*10+num3-i] # 第二次计算if ((f<10 && $[f*10+num4-i]==5));thenecho "${i}x111+5=$num" # 输出结果fifidone
done
解法2:分别对每个数进行枚举
#!/bin/bash
for ((i=6; i<=9; i++)) # 除数范围
dofor ((a=1; a<=9; a++)) # 千位数dofor ((b=0; b<=9; b++)) # 百位数doe=$[a*10+b-i] # 第一次计算# e是个位数if [ $e -lt 10 ];thenfor ((c=0; c<=9; c++)) # 十位数do# f是个位数f=$[e*10+c-i] # 第二次计算if [ $f -lt 10 ];thenfor ((d=0; d<=9; d++)) # 个位数doif [ $[f*10+d-i] -eq 5 ];then # 最终余数应为5echo "${i}x111+5=$[i*111+5]" # 输出结果fidonefidonefidonedone
done
性能对比:
$ time bash compute1.sh
9x111+5=1004
real 0m0.986s$ time bash compute2.sh
9x111+5=1004
real 0m0.014s
**思考:**compute2.sh时间更短是因为它通过数学分析减少了循环次数,直接从可能的除数开始枚举,避免了不必要的计算。
示例8:百钱买百鸡
中国古代数学家张丘建在他的《算经》中提出了著名的"百钱买百鸡问题":
- 鸡翁一,值钱五
- 鸡母一,值钱三
- 鸡雏三,值钱一
- 百钱买百鸡,问翁、母、雏各几何?
#!/bin/bash
# 鸡翁数量 cock_num
# 鸡母数量 hen_num
# 鸡雏数量 chick_num
# 鸡总数量 sum
sum=100
# 钱总数 money
money=100for ((cock_num=1; cock_num<=money/5; cock_num++))
dofor ((hen_num=1; hen_num<=money/3; hen_num++))dofor chick_num in {3..100..3} # 鸡雏数是3的倍数do# 判断数量和金额是否都满足条件if [ $[ cock_num+hen_num+chick_num ] -eq $sum -a \$[ cock_num*5+hen_num*3+chick_num/3 ] -eq $money ];thenecho "鸡翁:$cock_num 鸡母:$hen_num 鸡雏:$chick_num"echo "$cock_num*5+$hen_num*3+$chick_num/3=100"echofidonedone
done
执行结果:
鸡翁: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:三对情侣参加婚礼
3个新郎为A、B、C,3个新娘为X、Y、Z。得到的假话信息:
- A说他将和X结婚
- X说她的未婚夫是C
- C说他将和Z结婚
解题思路:
- 根据假话得出反向判定条件
- 多个人不能同时和一个人结婚
解法1:直接枚举
#!/bin/bash
for A in X Y Z
dofor B in X Y Zdofor C in X Y Zdo# 判断条件:所有人不能重复,且与假话相反if [ "$A" != "$B" -a "$A" != "$C" -a "$B" != "$C" -a \"$A" != "X" -a "$C" != "X" -a "$C" != "Z" ];thenecho "A 与 $A 结婚"echo "B 与 $B 结婚"echo "C 与 $C 结婚"fidonedone
done
解法2:使用数字表示,更高效
#!/bin/bash
# 将X Y Z 抽象为 1 2 3
# 定义函数,根据参数值返回对应字母
duixiang () {case $1 in1) echo X ;;2) echo Y ;;3) echo Z ;;esac
}for ((A=1; A<=3; A++))
dofor ((B=1; B<=3; B++))dofor ((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 ];thenecho "A 与 $(duixiang $A) 结婚"echo "B 与 $(duixiang $B) 结婚"echo "C 与 $(duixiang $C) 结婚"exitfidonedone
done
执行结果:
A 与 Z 结婚
B 与 X 结婚
C 与 Y 结婚
示例10:求a+aa+aaa+…aaaaa
例如:2+22+222+2222+22222(此时共有5个数相加),数字和数字最长位数由键盘输入。
#!/bin/bash
read -p "输入1-9之间一个数:" m
read -p "输入数字的最长位数:" n# 定义最终和
sum=0for ((i=1; i<=n; i++))
do# count是j位m的值,每次计算完count值设置为0count=0for ((j=1; j<=i; j++))do# 计算j位m的值count=$[count + m * 10**(j-1)]doneecho -n "$count "# 控制输出格式if [ $i -eq $n ];thenecho -n "= "elseecho -n "+ "fisum=$[sum + count]
done
echo $sum
执行结果:
输入1-9之间一个数:6
输入数字的最长位数:5
6 + 66 + 666 + 6666 + 66666 = 74070
示例11:排列组合
有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?
#!/bin/bash
# 组合数量为sum
sum=0echo -n "组合包括:"
for ((i=1; i<=4; i++))
dofor ((j=1; j<=4; j++))dofor ((k=1; k<=4; k++))do# 确保三位数互不相同if [ $i -ne $j -a $i -ne $k -a $j -ne $k ];thenecho -n "$i$j$k "sum=$[sum+1]fidonedone
done
echo
echo "组合总数量为:$sum"
执行结果:
组合包括:123 124 132 134 142 143 213 214 231 234 241 243 312 314 321 324 341 342 412 413 421 423 431 432
组合总数量为:24
for 循环语句实践案例
示例1:批量修改文件扩展名
# 创建测试文件
touch file-{1..3}.txt# 方法1:使用sed获取新文件名
#!/bin/bash
for file in $(ls *.txt)
donew_name=$(echo $file | sed 's/.txt$/.jpg/') # 替换扩展名mv $file ${new_name} # 重命名文件
done# 方法2:使用rename命令(更高效)
rename ".jpg" ".txt" *.jpg # 批量修改扩展名
rename "file-" "" *.txt # 删除文件名中的特定字符串
示例2:批量设置服务开机启动
#!/bin/bash
for service in crond atd chronyd
dosystemctl enable $service # 设置服务开机启动
done
示例3:批量重启多个主机sshd服务
#!/bin/bash
for host in server{1..10}
dossh root@$host systemctl restart sshd # 远程重启服务
done
示例4:批量备份文件
#!/bin/bash
for file in host*
docp $file /backup/${file}.bak # 备份文件并添加.bak后缀
done
示例5:扫描网络中在线主机
#!/bin/sh
for i in `seq 1 254`
doIP=172.25.254.$ping -c2 $IP &> /dev/null # 发送2个ping包if [ $? -eq 0 ];thenecho "$IP is online" >> node_online.txtelse echo "$IP is offline" >> node_offline.txtfi
done# 注意:添加&符号可实现并发扫描
示例6:监控主机磁盘使用情况
#!/bin/bash
hostfile=/tmp/hosts
if [ $(cat $hostfile | wc -l) -lt 1 ];thenecho $hostfile is empty.exit 1
fifor host in $(cat $hostfile)
do> disk.log # 清空日志文件echo "=============HOSTNAME: $host =============" >> disk.log# 获取磁盘使用率超过80%的分区ssh $host df -h | grep '^/dev/' | awk '{print $1,$5}' | \cut -f 1 -d% | awk '{if ($2 > 80) print $0}' >> disk.log# 如果有超过80%的分区,发送邮件报警if [ $(cat disk.log | wc -l) -gt 1 ];thencat disk.log | mail -s "$host: disk is greater than 80%" root@localhostfi
done
for 循环语句高级实战案例
示例1:MySQL数据库备份
# 创建数据库
#!/bin/bash
MYSQL_CMD='mysql -uroot -e '
for db in web1 web2
do${MYSQL_CMD} "create database $db;" # 创建数据库
done# 备份数据库
#!/bin/bash
MYSQL_CMD='mysqldump -uroot '
for db in web1 web2
do${MYSQL_CMD} $db > $db.sql # 备份数据库到文件
done
示例2:MySQL分库分表备份
# 创建表
#!/bin/bash
MYSQL_CMD='mysql -uroot $db -e '
for db in web1 web2
do# 创建表并插入测试数据${MYSQL_CMD} "create table test(id int,name varchar(16));insert into test values(1,'testdata')";
done# 备份表
#!/bin/bash
MYSQL_CMD='mysqldump -uroot $db'
for db in web1 web2
do${MYSQL_CMD} > ${db}_test.sql # 备份表数据
done
示例3:批量创建系统账号
#!/bin/bash
# 引入系统函数库
. /etc/init.d/functionsusers_info_file=/etc/users
users_list_file=/tmp/users[ ! -f $users_info_file ] && touch $users_info_fileexec < ${users_list_file} # 重定向输入
# 创建用户并设置密码
while read username
do# 生成随机密码(使用md5加密并截取)pass=$(echo "$user+$RANDOM" | md5sum | cut -c 3-11)# 添加用户并设置密码useradd $username &>/dev/null && chage -d 0 $username && \echo "$pass" | passwd --stdin $username &>/dev/null && \echo -e "username: $username \t password:$pass" >> $users_info_file# 根据返回值判断用户和密码是否添加成功if [ $? -eq 0 ];thenaction "$username is ok" /bin/true # 优雅显示成功elseaction "$username is fail" /bin/false # 优雅显示失败fi
doneecho "--------------------------"
cat $users_info_file # 显示创建的用户信息
Linux 系统产生随机数的6种方法
方法1:通过系统环境变量 RANDOM
echo "furongwang$RANDOM" | md5sum | cut -c 1-8 # 结合md5增强随机性
方法2:通过openssl产生随机数
openssl rand -base64 8 # 生成8位随机Base64字符串
openssl rand -base64 20 # 生成20位随机Base64字符串
方法3:通过date命令获得随机数
date +%s%N # 输出纳秒时间戳
方法4:通过设备文件生成随机数
head /dev/urandom | md5sum # 使用随机设备生成随机数
head /dev/random | md5sum # 使用高质量随机设备
方法5:通过UUID生成随机数
cat /proc/sys/kernel/random/uuid # 读取UUID
uuidgen # 生成UUID
方法6:使用expect附带的mkpasswd命令
mkpasswd -l 20 -d 4 -c 4 -C 4 -s 4 # 生成符合复杂度要求的密码
方法7:使用mktemp命令和sha1sum
mktemp -u | sha1sum # 创建临时文件路径并计算哈希值
select 循环语句介绍及语法
select循环主要用于创建交互式菜单,其语法结构如下:
select 变量名 [ in 菜单取值列表 ]
do指令...
done
特点:
in 菜单取值列表
可省略,省略时相当于使用in "$@"
- 执行后会显示数字序号菜单,等待用户选择
- 用户输入数字序号后执行相应循环体
- 默认提示符为
#?
,可通过PS3环境变量自定义 - 用户选择的序号保存在REPLY环境变量中
- select是无限循环,需要显式退出
select 循环语句案例
示例1:打印课程清单
#!/bin/bash
select course in mysql python linux
doecho $course # 输出选择的课程
done
示例2:更改提示符并配合case使用
#!/bin/bash
PS3="选择你的课程: " # 自定义选择提示符
select course in linux mysql python exit
doecho "你选择了第 $REPLY 个条目。" # REPLY保存用户选择的序号case $course inlinux)echo "linux xxx";;mysql)echo "mysql yyy";;python)echo "python zzz";;exit)exit # 退出循环;;esacecho
done
示例3:两个数计算器
#!/bin/bash
num1=10
num2=20
echo "There are two numbers $num2 and $num1."select method in 加法 减法 乘法 退出
docase $method in加法)echo $[num1 + num2] # 加法运算;;减法)echo $[num2 - num1] # 减法运算;;乘法)echo $[num1 * num2] # 乘法运算;;退出)exit # 退出程序;;*)echo "从1-4中选择" # 错误输入提示;;esac
done
示例4:一键安装mysql和httpd
#!/bin/bash
# 定义输出颜色函数
function print () {if [ "$1" == "PASS" ];thenecho -e '\033[1;32mPASS\033[0;39m' # 绿色elif [ "$1" == "FAIL" ];thenecho -e '\033[1;31mFAIL\033[0;39m' # 红色elif [ "$1" == "DONE" ];thenecho -e '\033[1;35mDONE\033[0;39m' # 紫色elseecho "Usage: print PASS|FAIL|DONE"fi
}# 定义服务安装函数
function InstallService () {case $1 inhttpd)echo -n "Start installing httpd..."{ yum install -y httpd # 安装httpdsystemctl enable httpd --now # 设置开机启动并立即启动echo "Hello World" > /var/www/html/index.html # 创建测试页面} &>/dev/null # 屏蔽输出[ $? -eq 0 ] && print DONE || print FAIL # 判断执行结果;;mysql)echo -n "Start installing mysql..."{ yum install -y mariadb-server # 安装mariadbsystemctl enable mariadb --now # 设置开机启动并立即启动} &>/dev/null # 屏蔽输出[ $? -eq 0 ] && print DONE || print FAIL # 判断执行结果;;esac
}# 主菜单
select app in "Install httpd" "Install mysql" "Exit"
docase $REPLY in1)InstallService httpd # 安装httpd;;2)InstallService mysql # 安装mysql;;3)exit # 退出程序;;*)echo "从1-3中选择。" # 错误输入提示;;esac
done
总结
for循环的核心要点:
- 两种语法结构:变量取值型和C语言型,各有适用场景
- 擅长处理已知迭代次数的循环任务
- 可以嵌套使用解决复杂问题
- 在企业环境中常用于批量操作、监控和自动化任务
select循环的核心要点:
- 专为创建交互式菜单设计
- 配合case语句可以实现复杂的交互逻辑
- 通过PS3和REPLY环境变量自定义提示符和获取用户选择
- 适合编写用户友好的命令行工具