深入理解 Shell 循环与函数:语法、示例及综合应用
一、循环语句
循环语句用于重复执行一组命令,根据不同的场景(如固定范围、条件判断、嵌套执行)可选择不同的循环类型。
1、for 循环详解
核心作用 :读取取值列表中的多个变量值,逐一对每个值执行同一组命令,适用于数据有明确范围或列表的场景。
语法格式
bash
for 变量名 in 取值列表
do
命令序列(命令行)
done
# 或支持C语言风格的数值循环(适用于固定步长的数值范围)
for ((初始化表达式;条件表达式;更新表达式))
do
命令序列
done
示例1:批量创建用户
通过读取用户列表文件,批量创建用户并设置统一密码。
-
第一步:创建用户列表文件
users.txt
bashvim /root/users.txt # 文件内容(每个用户单独一行) jim bob marry rose mike
-
第二步:编写批量创建用户脚本
a.sh
bashvim /tmp/a.sh #!/bin/bash # 读取用户输入的统一密码,-p 显示提示信息 read -p "enter the user password:" pas # 循环读取 users.txt 中的每个用户名 for UNAME in `cat /root/users.txt` do useradd $users # 创建用户 echo "$pas"| passwd --stdin $i # 批量设置密码(--stdin 支持管道传值) done
-
执行脚本:需赋予执行权限并以 root 身份运行
ini[root@hrz2 tmp]# chmod +x /root/addusers.sh [root@hrz2 tmp]# ./a.sh 请为jim输入密码1qaz2wsx useradd:用户"jim"已存在 更改用户 jim 的密码 。 passwd:所有的身份验证令牌已经成功更新。 请为bob输入密码3edc4rfv useradd:用户"bob"已存在 更改用户 bob 的密码 。 passwd:所有的身份验证令牌已经成功更新。 请为marry输入密码5tgb6yhn useradd:用户"marry"已存在 更改用户 marry 的密码 。 passwd:所有的身份验证令牌已经成功更新。 请为rose输入密码7ujm8ik, useradd:用户"rose"已存在 更改用户 rose 的密码 。 passwd:所有的身份验证令牌已经成功更新。 请为mike输入密码9ol.0p;/ useradd:用户"mike"已存在 更改用户 mike 的密码 。 passwd:所有的身份验证令牌已经成功更新。
示例2:数值范围循环(固定步长)
-
示例2.1:循环输出 1-10(步长为1)
bash#!/bin/bash for ((i=1;i<=10;i++)) # i初始为1,i<=10时循环,每次i自增1 do echo $i done
-
示例2.2:循环输出 1-10 中的奇数(步长为2)
bash#!/bin/bash for ((i=1;i<=10;i+=2)) # 每次i自增2 do echo $i done
示例3:计算 1-100 的整数和
通过 for 循环累加求和,需定义"和变量"存储中间结果。
bash
#!/bin/bash
a=0 # 初始化"和变量",初始值为0
for ((i=1;i<=100;i++)) # i从1循环到100
do
a=$(($a+$i)) # 每次将i的值累加到a中(等价于 a=$((a+i)) )
done
echo "1-100所有整数相加的和为$a" # 输出最终结果(结果为5050)
示例4:根据 IP 地址检测主机状态
读取 IP 列表文件,通过 ping
命令检测主机连通性。
-
第一步:创建 IP 列表文件
iplist
bash[root@hrz2 tmp]# vim /root/iplist # 文件内容(每个IP单独一行) 192.168.100.10 192.168.100.20 192.168.100.30
-
第二步:编写检测脚本
b.sh
bashvim /root/b.sh #!/bin/bash # 读取 iplist 中的所有IP(存储到变量ip) ip=$(cat /root/iplist) # 循环遍历每个IP for i in $ip do # ping 3次(-c 3),所有输出重定向到/dev/null(不显示冗余信息) ping -c 3 $i &>/dev/null # 判断 ping 命令执行结果($? 为0表示执行成功,即主机连通) if [ $? -eq 0 ];then echo "$i 通" else echo "$i 不通" fi done
示例5:查找 100-1000 以内的水仙花数
水仙花数:一个 3 位数,其各位数字的立方和等于该数本身(如 153 = 1³ + 5³ + 3³)。
bash
#!/bin/bash
echo "100-1000以内的水仙花数:"
for ((n=100;n<=1000;n++))
do
# 提取百位数字(n除以100取整)
bai=$((n/100))
# 提取十位数字(n除以10取余后,再除以10取整)
shi=$(((n%100)/10))
# 提取个位数字(n除以10取余)
ge=$((n%10))
# 计算各位数字的立方和
sum=$((bai*bai*bai + shi*shi*shi + ge*ge*ge))
# 判断立方和是否等于原数,若是则输出
if [ $sum -eq $n ];then
echo $n
fi
done
执行结果:
ini
[root@hrz2 tmp]# ./sxhs.sh
153 是水仙花数
370 是水仙花数
371 是水仙花数
407 是水仙花数
2、while 语句
核心作用 :重复测试某个条件,只要条件成立(返回0),就反复执行循环体内的命令;条件不成立则退出循环。
语法格式
bash
while [ 条件表达式 ] # 或其他条件判断(如 ((条件)) )
do
命令序列
done
示例1:显示 0-10 的所有整数
通过变量自增控制循环,条件为"变量 ≤10"。
bash
#!/bin/bash
a=0 # 初始化变量a为0
while [ $a -le 10 ] # 条件:a≤10
do
echo $a # 输出当前a的值
let a++ # 变量a自增1(等价于 a=$((a+1)) )
done
示例2:求 1-100 的整数和
通过 while
循环累加,变量 n
控制范围,i
存储累加和。
bash
#!/bin/bash
i=0 # 存储累加和,初始为0
n=1 # 控制循环范围,从1开始
while [ $n -le 100 ] # 条件:n≤100
do
i=$(( $i + $n )) # 累加:将n的值加到i中
let n++ # n自增1
done
echo "1-100的和为:$i" # 输出结果(5050)
示例3:随机猜数字游戏(0-999)
利用 $RANDOM
生成随机数($RANDOM
范围为 0-32767),通过 while true
实现无限循环,直到猜对为止。
-
随机数生成方法:
expr $RANDOM % 1000
(取 0-999 的随机数)bash# 测试随机数生成 [root@rhel8 ~]# expr $RANDOM % 1000 456 [root@rhel8 ~]# expr $RANDOM % 1000 89
-
猜数字脚本
bash#!/bin/bash # 生成0-999的随机数 num=$(expr $RANDOM % 1000) a=0 # 统计猜的次数 echo "数字范围为0-999的整数,猜猜看" while true # 无限循环(直到猜对退出) do read -p "请输入你猜的整数:" n let a++ # 每次输入后,次数+1 # 判断猜测结果 if [ $n -eq $num ];then echo "恭喜你答对了,数字为$num" echo "你总共猜了$a次" exit 0 # 猜对,退出脚本 elif [ $n -gt $num ];then echo "你猜高了" else echo "你猜低了" fi done
3、until 语句
核心作用 :与 while
相反,重复测试某个条件 ,只要条件不成立(返回非0),就反复执行循环体;条件成立则退出循环。
语法格式
bash
until [ 条件表达式 ]
do
命令序列
done
示例1:求 1-100 的整数和
通过 until
实现:当 n > 100
时条件成立,退出循环(即循环执行到 n=100
)。
bash
#!/bin/bash
i=0 # 存储累加和
n=1 # 控制循环范围
until [ $n -gt 100 ] # 条件:n>100(不成立时执行循环)
do
i=$((i + n)) # 累加
let n++ # n自增1
done
echo "1-100的和为:$i" # 输出结果(5050)
4、双 for 循环(嵌套循环)
核心作用:在一个循环(外循环)内部嵌套另一个循环(内循环),适用于"多维数据处理"(如矩阵、乘法表)。
基础嵌套示例
外循环控制变量 a
(1-5),内循环控制变量 b
(1-5),外循环每执行1次,内循环执行5次。
bash
#!/bin/bash
for ((a=1;a<=5;a++)) # 外循环
do
echo "a=$a"
for ((b=1;b<=5;b++)) # 内循环(嵌套在外循环中)
do
echo "b=$b"
done
done
执行结果:
a=1
b=1
b=2
b=3
b=4
b=5
a=2
b=1
...(以此类推)
示例:打印 99 乘法表
通过嵌套循环实现:外循环控制"行数"(1-9),内循环控制"每行的列数"(1-当前行数)。
-
for 循环实现
bash#!/bin/bash for ((i=1;i<=9;i++ )) # 外循环:控制行数(1-9) do for ((j=1;j<=i;j++)) # 内循环:控制每行的列数(1-i) do # -n 不换行,输出"j * i = 结果" echo -n "$j * $i = `expr $[i*j]` " done echo "" # 每行结束后换行 done
-
while 循环实现
bash#!/bin/bash a=1 # 外循环变量(行数) while [ $a -le 9 ] do b=1 # 内循环变量(列数) while [ $b -le $a ] do echo -n "$b * $a = `expr $[a*b]` " let b++ done echo # 换行 let a++ done
执行结果(99乘法表):
1 * 1 = 1
1 * 2 = 2 2 * 2 = 4
1 * 3 = 3 2 * 3 = 6 3 * 3 = 9
...(以此类推至9行)
5、break 和 continue(循环控制)
break
和 continue
用于控制循环的执行流程,前者终止循环,后者跳过当前迭代。
(1)break:终止当前循环
作用 :一旦执行 break
,立即跳出当前所在的循环 (若为嵌套循环,仅跳出内循环,外循环继续),且 break
之后的语句不再执行。
示例:嵌套循环中用 break
终止内循环
bash
#!/bin/bash
# break 示例:内循环到b=4时终止
for(( a=1;a<=5; a++ )) # 外循环
do
echo "outside $a"
for ((b=1;b<=5;b++ )) # 内循环
do
if [ $b -eq 4 ] # 条件:b=4时执行break
then
break # 跳出内循环,不再执行b=4、b=5的逻辑
fi
echo "inside $b "
done
done
执行结果(内循环仅执行到b=3):
outside 1
inside 1
inside 2
inside 3
outside 2
inside 1
inside 2
inside 3
...(外循环继续,内循环均到b=3终止)
(2)continue:跳过当前迭代
作用 :执行 continue
后,跳过当前迭代中 continue 之后的语句,直接进入下一次迭代(不终止整个循环)。
示例:跳过 5-9 的数字,输出 1-15 中其他数字
bash
#!/bin/bash
# continue 示例:跳过5-9
for (( a=1;a<=15;a++))
do
if [ $a -gt 5 ] && [ $a -lt 10 ] # 条件:a在6-9之间
then
continue # 跳过当前迭代,不执行echo
fi
echo " $a "
done
执行结果(跳过6-9):
1
2
3
4
5
10
11
12
13
14
15
二、函数
函数是将一组重复使用的命令序列封装成的"代码块",可通过函数名调用,提高脚本的复用性和可读性。
1、Shell 函数定义格式
Shell 函数有两种定义方式,本质无区别,推荐第二种(更简洁)。
格式1:带 function
关键字
bash
function 函数名 {
命令序列 # 函数体:需执行的命令
}
格式2:省略 function
(推荐)
bash
函数名() {
命令序列
}
2、函数返回值
函数执行完成后,可通过 return
或 echo
返回结果,两者适用场景不同。
(1)return 关键字
- 作用 :退出函数并返回一个"退出状态码"(范围:0-255),脚本中通过
$?
变量获取该值。 - 注意事项 :
- 必须在函数执行刚结束时 用
$?
获取返回值($?
仅记录最后一条命令的状态码)。 - 返回值超出 0-255 时,会自动对 256 取余(如返回 257 等价于 1,返回 256 等价于 0)。
- 必须在函数执行刚结束时 用
示例:函数返回输入值的2倍
bash
#!/bin/bash
# 定义函数 cy
function cy {
read -p "请输入任意一个整数值:" n
return $[$n*2] # 返回 n*2 的结果
}
cy # 调用函数
echo "$?" # 获取并输出返回值
执行结果(验证返回值范围):
shell
[root@hrz2 tmp]# ./aa.sh
请输入任意一个整数值:40
80 # 40*2=80(在0-255范围内,正常返回)
[root@hrz2 tmp]# ./aa.sh
请输入任意一个整数值:250
244 # 250*2=500,500%256=244(超出范围,取余返回)
[root@hrz2 tmp]# ./aa.sh
请输入任意一个整数值:256
0 # 256*2=512,512%256=0(取余返回)
(2)echo 直接返回
- 作用 :通过
echo
在函数体内输出结果,脚本中通过"变量赋值"获取返回值(适用于返回字符串或超出 0-255 的数值)。 - 示例:函数返回两数之和(用 echo)
bash
#!/bin/bash
sum() {
result=$[$1 + $2]
echo $result # 直接输出结果
}
# 调用函数并将结果赋值给变量 s
s=$(sum 10 20)
echo "两数之和:$s" # 输出:两数之和:30
3、函数传参与变量作用范围
(1)函数传参
- 原理 :调用函数时,在函数名后紧跟参数(用空格分隔),函数内部通过
$1
(第一个参数)、$2
(第二个参数)、$n
(第n个参数)获取。
示例:函数计算两数之和(通过参数传递)
bash
[root@hrz2 tmp]# vim bb.sh
#!/bin/bash
# 定义求和函数 sum1
sum1 () {
sum=$[ $1 + $2 ] # $1 接收第一个参数,$2 接收第二个参数
echo $sum
}
sum1 $1 $2 # 调用函数,将脚本的 $1、$2 传递给函数
执行结果:
shell
[root@hrz2 tmp]# ./bb.sh 20 30 # 脚本参数:20($1)、30($2)
50 # 20+30=50
[root@rhel8 ~]# ./bb.sh 1 6
7 # 1+6=7
(2)变量作用范围
- 全局变量:默认情况下,Shell 脚本中的变量为全局变量(在脚本所有位置(包括函数内)都有效)。
- 局部变量 :用
local
关键字在函数内定义的变量,仅在当前函数内有效,函数外无法访问。
示例1:全局变量 vs 局部变量
shell
#!/bin/bash
abc () {
echo "函数内的未经过local的变量i值$i" # 访问全局变量i
local i # 定义局部变量i(仅在函数内有效)
i=6
echo "函数内的变量i值是$i" # 访问局部变量i
}
i=9 # 全局变量i
abc # 调用函数
echo "函数外面的变量i值是$i" # 访问全局变量i(不受函数内局部变量影响)
执行结果:
shell
[root@hrz2 tmp]# ./cc.sh
函数内的未经过local的变量i值9 # 函数内先访问全局变量i=9
函数内的变量i值是6 # 局部变量i=6生效
函数外面的变量i值是9 # 全局变量i仍为9(局部变量不影响)
示例2:局部变量的执行顺序
bash
#!/bin/bash
abc () {
echo "inside1 $i" # 访问全局变量i=9
let i++ # 全局变量i自增为10
local i # 定义局部变量i(后续访问局部变量)
i=8
echo "inside2 $i" # 访问局部变量i=8
}
i=9 # 全局变量i
abc # 调用函数
echo "outside $i" # 全局变量i已被let i++改为10
执行结果:
shell
[root@hrz2 tmp]# ./cc.sh
inside1 9 # 先访问全局变量i=9
inside2 8 # 局部变量i=8生效
outside 10 # 全局变量i已自增为10
4、递归函数
递归:函数内部调用自身,需设置"终止条件"(避免无限递归),适用于阶乘、斐波那契数列等场景。
示例:递归计算阶乘(n! = n × (n-1) × ... × 1)
shell
#!/bin/bash
# 定义递归函数 cy
function cy() {
if [ $1 -eq 1 ];then # 终止条件:当参数为1时,返回1
echo 1
else
local temp=$[ $1 - 1 ] # 计算n-1(如n=5时,temp=4)
local result=`cy $temp` # 递归调用自身,计算(n-1)!
echo $[ result * $1 ] # 计算n! = (n-1)! × n
fi
}
read -p "输入一个值:" vaule # 读取用户输入的数值
result=`cy $vaule` # 调用递归函数
echo "阶乘的值为: $result" # 输出结果
执行结果:
shell
[root@hrz2 tmp]#./dd.sh
输入一个值:5
阶乘的值为: 120 # 5! = 5×4×3×2×1=120
[root@hrz2 tmp]# ./dd.sh
输入一个值:3
阶乘的值为: 6 # 3! = 3×2×1=6
5、函数库
函数库:将多个常用函数集中存储在一个脚本文件中,其他脚本通过"引入"该文件直接调用函数,避免重复编写代码。
步骤1:创建函数库(存储加减乘除函数)
创建 ee.sh
作为函数库,包含 jia
(加)、jian
(减)、cheng
(乘)、chu
(除)4个函数:
bash
[root@hrz2 tmp]# cat ee.sh
#!/bin/bash
# ee.sh:Shell函数库(加减乘除)
# 加法函数
jia() {
result=$[ $1 + $2 ]
echo "$result"
}
# 减法函数
jian() {
result=$[ $1 - $2 ]
echo "$result"
}
# 乘法函数
cheng() {
result=$[ $1 * $2 ]
echo "$result"
}
# 除法函数(判断分母不为0)
chu() {
if [ $2 -ne 0 ];then
result=$[ $1 / $2 ]
echo "$result"
else
echo "除法中分母不能为0"
fi
}
步骤2:调用函数库
创建 ff.sh
脚本,通过 . /root/ee.sh
(或 source /root/ee.sh
)引入函数库,然后调用函数:
bash
[root@hrz2 tmp]# cat ff.sh
#!/bin/bash
# 引入函数库(需指定绝对路径)
. /root/ee.sh
# 读取用户输入的两个数字
read -p "请输入第一个数字:" n
read -p "请输入第二个数字:" m
# 调用函数库中的函数,获取结果
result1=`jia $n $m`
result2=`jian $n $m`
result3=`cheng $n $m`
result4=`chu $n $m`
# 输出结果
echo "两数之和为: $result1"
echo "两数之差为: $result2"
echo "两数之积为: $result3"
echo "两数之商为: $result4"
执行结果
bash
[root@hrz2 tmp]# chmod +x ff.sh
[root@hrz2 tmp]# ./ff.sh
请输入第一个数字:5
请输入第二个数字:2
两数之和为: 7
两数之差为: 3
两数之积为: 10
两数之商为: 2
6、综合案例:Shell 计算器
整合函数库思想,实现一个支持"加减乘除取余"、"整数验证"、"循环计算"的计算器。
步骤1:创建核心功能函数脚本 jjcc.sh
bash
[root@hrz2 tmp]# vim /tmp/jjcc.sh
#!/bin/bash
# 函数1:获取第一个整数(验证整数格式,支持输入end退出)
getone(){
read -p "请输入一个整数,输入end退出" "num1"
# 输入end则退出脚本
if [ "$num1" = "end" ];then
echo " 再见"
exit
fi
# 验证输入是否为整数(expr处理非整数会返回非0状态码)
expr "$num1" + 1 > /dev/null 2>&1
if [ $? -ne 0 ];then
echo '输入的参数必须是整数'
getone # 输入非法,重新获取
fi
}
# 函数2:选择运算符号(验证符号合法性)
fuhao() {
echo "请选择符号"
# 显示可选运算符
cat <<EOF
+ 加
- 减
* 乘
/ 除
% 余
EOF
read "fh"
# 验证运算符是否合法
if [ "$fh" = "+" -o "$fh" = "-" -o "$fh" = "*" -o "$fh" = "/" -o "$fh" = "%" ];then
echo "--------------------------"
echo "$num1 $fh"
echo "--------------------------"
else
echo "未知符号请按照以上列表重新输入"
echo "$num1"
fuhao # 符号非法,重新选择
fi
}
# 函数3:获取第二个整数(同getone逻辑)
gettwo(){
read -p "请再次输入一个整数,输入end退出" "num2"
if [ "$num2" = "end" ];then
echo " 再见"
exit
fi
expr "$num2" + 1 > /dev/null 2>&1
if [ $? -ne 0 ];then
echo '输入的参数必须是整数'
gettwo
fi
}
# 函数4:计算结果(处理除法/取余的分母为0问题)
jieguo(){
if [ "$fh" = "+" ];then
zhi=$[ $num1 + $num2 ]
elif [ "$fh" = "-" ];then
zhi=$[ $num1 - $num2 ]
elif [ "$fh" = "*" ];then
zhi=$[ $num1 * $num2 ]
elif [ "$fh" = "/" ];then
if [ $num2 -ne 0 ];then
zhi=$[ $num1 / $num2 ]
else
echo "分母不许为0"
fi
elif [ "$fh" = "%" ];then
if [ $num2 -ne 0 ];then
zhi=$[ $num1 % $num2 ]
else
echo "分母不许为0"
fi
fi
}
步骤2:创建计算器主脚本 jsq.sh
shell
[root@hrz2 tmp]# vim jsq.sh
#!/bin/bash
# 引入核心功能函数脚本
. /tmp/jjcc.sh
# 显示欢迎信息
echo "======================"
echo "欢迎使用计算器"
echo "======================"
# 初始化全局变量(存储计算数据)
num1=0 # 第一个运算数
num2=0 # 第二个运算数
zhi=0 # 计算结果
fh= # 运算符
# 无限循环:支持多次计算
while true
do
# 重置变量(避免上一次计算结果干扰)
num1=0
num2=0
zhi=0
fh=
echo "--------------------------"
echo "$num1"
echo "--------------------------"
getone $num1 # 获取第一个整数
echo "--------------------------"
echo "$num1"
echo "--------------------------"
fuhao $fh # 选择运算符号
gettwo $num2 # 获取第二个整数
echo "--------------------------"
echo "$num1 $fh $num2 = " # 显示运算表达式
echo "--------------------------"
jieguo $zhi # 执行计算
echo "$zhi" # 输出结果
# 询问是否继续计算
read -p "按y继续使用/回车退出" yn
if [ "$yn" != "y" ];then
echo "感谢使用"
break # 退出循环
exit # 退出脚本
fi
done
执行计算器
shell
[root@hrz2 tmp]# chmod +x jsq.sh
[root@hrz2 tmp]# ./jsq.sh
======================
欢迎使用计算器
======================
--------------------------
0
--------------------------
请输入一个整数(输入end 退出)/n20
--------------------------
20
--------------------------
请选择符号
+ 加
- 减
* 乘
/ 除
% 余
+
--------------------------
20 +
--------------------------
请再次输入一个整数(输入end 退出)\n^C
[root@hrz2 tmp]# vim jjcc.sh
[root@hrz2 tmp]# ./jsq.sh
======================
欢迎使用计算器
======================
--------------------------
0
--------------------------
请输入一个整数(输入end 退出)200
--------------------------
200
--------------------------
请选择符号
+ 加
- 减
* 乘
/ 除
% 余
+
--------------------------
200 +
--------------------------
请再次输入一个整数(输入end 退出)512
--------------------------
200 + 512 =
--------------------------
712
按y继续使/回车退出
感谢使用