Shell 脚本:循环与函数
Shell 脚本:循环与函数
一、循环结构:重复执行命令的核心
循环是 Shell 脚本中用于重复执行一组命令的结构,根据场景可分为 for、while、until 三种,配合 break/continue 可灵活控制循环流程。
1. for 循环:遍历取值列表
for 循环适用于已知取值范围的场景(如遍历文件、列表、数字序列),支持 “基础列表遍历” 和 “C 风格循环”,也可嵌套实现复杂逻辑(双 for 循环)。
(1)基础语法
|
类型 |
语法格式 |
说明 |
|
列表遍历 |
for 变量名 in 取值列表; do 命令序列; done |
变量依次取 “取值列表” 中的值,执行命令 |
|
C 风格循环 |
for ((初始值; 条件; 步长)); do 命令序列; done |
类似 C 语言,适合数字范围循环(如 1~9) |
(2)实战案例
案例 1:批量创建用户(列表遍历)
需求:从 users.txt 文件中读取用户名,批量创建用户并设置独立密码。
- 先创建用户列表文件 users.txt:
# 编辑用户列表(每个用户名占一行) vim /root/users.txt jim bob marry rose mike - 编写脚本 b.sh:
#!/bin/bash
# 批量创建用户脚本
for username in $(cat /root/users.txt); do # 遍历文件中的用户名
read -p "请为用户 $username 设置密码:" pass # 交互式输入密码
useradd $username # 创建用户
echo "$pass" | passwd --stdin $username # 批量设置密码(避免交互)
echo "用户 $username 创建完成!"
done
- 执行:sh b.sh,按提示输入每个用户的密码即可批量创建。
案例 2:九九乘法表(双 for 循环嵌套)
需求:用嵌套 for 循环输出标准九九乘法表,通过制表符 \t 对齐。
#!/bin/bash
# 九九乘法表脚本
for ((i=1; i<=9; i++)); do # 外层循环:控制行数(1~9)
for ((j=1; j<=i; j++)); do # 内层循环:控制每行的列数(1~当前行数i)
product=$((j * i)) # 计算乘积
echo -n -e "$j×$i=$product\t" # -n 不换行,-e 解析转义符(\t)
done
echo # 每行结束后换行
done
- 执行效果:
1×1=1
1×2=2 2×2=4
1×3=3 2×3=6 3×3=9
1×4=4 2×4=8 3×4=12 4×4=16
1×5=5 2×5=10 3×5=15 4×5=20 5×5=25
1×6=6 2×6=12 3×6=18 4×6=24 5×6=30 6×6=36
1×7=7 2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49
1×8=8 2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64
1×9=9 2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81
2. while 循环:条件成立则重复执行
while 循环适用于条件未知的场景(如等待用户输入、循环到某个条件满足),核心逻辑是 “条件为真则执行循环体”。
(1)基础语法
while 条件表达式; do
命令序列 # 条件为真时执行
done
(2)实战案例
案例 1:计算 1~100 的整数和
需求:从 1 累加至 100,用 while 循环控制变量递增。
#!/bin/bash
# 计算 1~100 的和
a=1 # 初始值(从 1 开始)
sum=0 # 累加结果(初始为 0)
while [ $a -le 100 ]; do # 条件:a ≤ 100 时执行循环
sum=$((sum + a)) # 累加:sum = 原 sum + 当前 a
a=$((a + 1)) # a 自增 1(步长为 1)
done
echo "1~100 的整数和为:$sum"
# 输出结果:5050
案例 2:随机猜数字游戏
需求:生成 0~999 的随机数,让用户猜数字,提示 “大了”“小了”,直到猜对并统计次数。
#!/bin/bash
# 随机猜数字游戏(0~999)
num=$((RANDOM % 1000)) # 生成随机数(RANDOM 是 Bash 内置随机数,%1000 限定 0~999)
a=0 # 统计猜的次数
while true; do # 无限循环(直到猜对后 exit)
read -p "请猜一个 0~999 的整数:" n # 接收用户输入
let a++ # 次数自增
# 判断猜的结果
if [ $n -eq $num ]; then
echo "恭喜猜对了!正确数字是 $n"
echo "你总共猜了 $a 次"
exit # 猜对后退出循环
elif [ $n -gt $num ]; then
echo "猜大了,再试试!"
else
echo "猜小了,再试试!"
fi
done
3. until 循环:条件不成立则重复执行
until 循环与 while 逻辑完全相反:“条件为假时执行循环体,条件为真时退出循环”,适合 “直到某个条件满足才停止” 的场景。
(1)基础语法
until 条件表达式; do
命令序列 # 条件为假时执行
done
(2)实战案例:计算 1~100 的整数和(until 版)
需求:与 while 案例功能一致,仅替换循环类型。
#!/bin/bash
# 用 until 计算 1~100 的和
a=1
sum=0
# 条件:a > 100 时退出循环(即 a ≤ 100 时执行循环)
until [ $a -gt 100 ]; do
sum=$((sum + a))
a=$((a + 1))
done
echo "1~100 的整数和为:$sum"
# 结果同样为 5050
4. 循环控制:break 与 continue
break 和 continue 用于在循环中主动控制流程,避免无效循环或跳过部分迭代。
|
命令 |
作用 |
示例场景 |
|
break |
立即跳出当前所在的循环(若嵌套循环,仅跳出内层) |
猜数字游戏猜对后跳出循环 |
|
continue |
跳过当前迭代的剩余命令,直接进入下一次迭代 |
遍历 1~15 时,跳过 6~9 |
(1)break 案例:嵌套循环中跳出内层
需求:外层循环 1~5,内层循环 1~5,当内层变量 b=4 时跳出内层循环。
#!/bin/bash
for ((a=1; a<=5; a++)); do # 外层循环
echo "外层循环:a = $a"
for ((b=1; b<=5; b++)); do # 内层循环
if [ $b -eq 4 ]; then
break # 当 b=4 时,跳出内层循环(不执行 b=4、5)
fi
echo " 内层循环:b = $b"
done
done
- 执行效果(内层仅输出 b=1~3):
外层循环:a = 1
内层循环:b = 1
内层循环:b = 2
内层循环:b = 3
外层循环:a = 2
内层循环:b = 1
...(后续省略)
(2)continue 案例:跳过指定范围的迭代
需求:遍历 1~15,跳过 6~9 的数字(不输出)。
#!/bin/bash
for ((a=1; a<=15; a++)); do
# 条件:a 在 6~9 之间时,跳过当前迭代
if [ $a -gt 5 ] && [ $a -lt 10 ]; then
continue
fi
echo "当前数字:$a"
done
- 执行效果(跳过 6~9):
当前数字:1
当前数字:2
...
当前数字:5
当前数字:10
当前数字:11
...
二、函数:封装可复用的代码块
函数是 Shell 中用于封装重复逻辑的代码块,可接收参数、返回结果,支持局部变量和递归,大幅提升脚本的复用性和可读性。
1. 函数定义:两种合法格式
Shell 函数定义有两种格式,function 关键字可省略(推荐省略,更简洁)。
|
格式 |
语法 |
说明 |
|
带 function |
function 函数名 { 命令序列; } 或 function 函数名() { 命令序列; } |
兼容性好,支持 ksh/zsh 等 Shell |
|
省略 function |
函数名() { 命令序列; } |
Bash 推荐格式,更简洁,符合 POSIX 标准 |
2. 函数返回值:return 与 echo
函数的 “返回值” 有两种实现方式,适用于不同场景:
(1)return:返回退出状态码(0~255)
- return 数值:用于返回执行结果状态(0 表示成功,非 0 表示失败),或小范围整数(0~255)。
- 脚本中通过 $? 获取返回值($? 仅保存上一条命令的退出码,需立即获取)。
- 注意:若数值超出 0~255,会自动取模 256(如 return 257 实际返回 1)。
案例:返回输入整数的 2 倍
#!/bin/bash
# 函数:返回输入整数的 2 倍
num() {
read -p "请输入一个整数:" n
return $((n * 2)) # 返回 n*2 的结果(需在 0~255 内)
}
num # 调用函数
echo "函数返回值(n*2):$?" # 立即获取返回值
- 执行示例:
请输入一个整数:20 → 输出:40(20*2=40)
请输入一个整数:130 → 输出:4(130*2=260,260%256=4)
请输入一个整数:256 → 输出:0
(2)echo:返回任意结果(推荐)
- 若需返回超出 0~255 的值(如大整数、字符串),直接用 echo 输出结果,脚本中通过命令替换($(函数名))捕获。
案例:返回输入整数的 2 倍(无范围限制)
#!/bin/bash
# 函数:用 echo 返回输入整数的 2 倍
num() {
read -p "请输入一个整数:" n
echo $((n * 2)) # 直接输出结果
}
result=$(num) # 捕获 echo 的输出
echo "函数返回值(n*2):$result" # 输出结果(无范围限制)
- 执行示例:
请输入一个整数:300 → 输出:600(无范围限制)
3. 函数传参:向函数传递参数
函数可像脚本一样接收参数,通过 $1、$2、$*、$@ 等特殊变量获取参数($0 表示函数名,非脚本名)。
语法:调用函数时传递参数
# 定义函数(接收 2 个参数,计算和)
add() {
sum=$(( $1 + $2 )) # $1 是第一个参数,$2 是第二个参数
echo $sum
}
# 调用函数并传递参数(20 和 30 是参数)
add 20 30
运行:sh a.sh 20 30
# 输出:50
实战案例:计算两个参数的和
#!/bin/bash
# 函数:计算两个参数的和
add() {
if [ $# -ne 2 ]; then # 判断参数数量是否为 2
echo "错误:需传入 2 个参数!"
return 1 # 返回非 0 表示失败
fi
echo $(( $1 + $2 ))
}
# 调用函数(传递脚本的参数 $1 和 $2)
add $1 $2
4. 函数变量作用域:局部变量与全局变量
Shell 变量默认是全局变量(整个脚本生效),若需限定变量仅在函数内生效,需用 local 关键字声明为局部变量。
|
变量类型 |
声明方式 |
作用范围 |
示例 |
|
全局变量 |
直接赋值(如 a=10) |
整个脚本(包括所有函数) |
a=10 在函数内外都可访问 |
|
局部变量 |
local 变量名=值 |
仅当前函数内生效 |
函数内 local a=10,函数外无法访问 |
实战案例:区分局部与全局变量
需求:函数内用局部变量 m/n 计算和,函数外用全局变量 m/n 计算和,分别输出。
#!/bin/bash
# 函数:用局部变量计算和
sum1() {
sum=$((m + n))
local m n # 声明局部变量(仅函数内生效)
m=20
n=10
local sum=$((m + n)) # 局部变量 sum
echo "局部变量 m=$m, n=$n,和为:$sum"
}
# 全局变量(整个脚本生效)
m=20
n=20
# 调用函数并输出结果
sum1
echo "全局变量 m=$m, n=$n,和为:$sum"
- 执行效果:
局部变量 m=20, n=10,和为:30
全局变量 m=20, n=20,和为:40
5. 递归函数:函数调用自身
递归是 “函数调用自身” 的特性,适用于分治问题(如阶乘、斐波那契数列),需注意设置 “递归终止条件”(避免无限递归)。
实战案例:计算一个数的阶乘
需求:输入一个正整数,用递归计算其阶乘(阶乘定义:n! = n × (n-1) × ... × 1,终止条件:1! = 1)。
#!/bin/bash
# 递归函数:计算阶乘
abc() {
local n=$1 # 接收参数(当前数字)
# 终止条件:n=1 时返回 1
if [ $n -eq 1 ]; then
echo 1
return
fi
# 递归调用:n! = n × (n-1)!
local result=$(abc $((n - 1))) # 调用自身,计算 (n-1)!
echo $((n * result)) # 返回 n × (n-1)! 的结果
}
# 接收用户输入并调用函数
read -p "请输入一个正整数:" num
result=$(abc $num)
echo "$num 的阶乘为:$result"
- 执行示例:
请输入一个正整数:5 → 输出:5 的阶乘为:120(5×4×3×2×1=120)
请输入一个正整数:3 → 输出:3 的阶乘为:6(3×2×1=6)
6. 函数库:封装通用函数供复用
函数库是 “集中存放多个函数的脚本文件”,其他脚本可通过 “ source 命令” 加载函数库,直接调用函数,避免重复编写代码。
(1)函数库的核心逻辑
- 独立文件:将通用函数(如加减乘除)集中写入一个脚本(如 func_lib.sh),作为 “函数库”;
- 加载调用:其他脚本通过 source 函数库路径 或 . 函数库路径 加载函数,无需重新定义即可调用。
(2)实战案例:搭建 “加减乘除” 函数库
步骤 1:创建函数库文件 func_lib.sh
#!/bin/bash
# 函数库:包含加减乘除四个通用函数
# 1. 加法函数:返回 $1 + $2 的结果
add() {
local num1=$1
local num2=$2
echo $((num1 + num2))
}
# 2. 减法函数:返回 $1 - $2 的结果
subtract() {
local num1=$1
local num2=$2
echo $((num1 - num2))
}
# 3. 乘法函数:返回 $1 × $2 的结果
multiply() {
local num1=$1
local num2=$2
echo $((num1 * num2))
}
# 4. 除法函数:返回 $1 ÷ $2 的结果(处理分母为 0 的情况)
divide() {
local num1=$1
local num2=$2
if [ $num2 -eq 0 ]; then # 判断分母是否为 0
echo "错误:除法分母不能为 0"
return 1 # 返回非 0 表示执行失败
fi
echo $((num1 / num2)) # 整数除法(若需小数,需用 bc 工具)
}
步骤 2:创建调用脚本 calc.sh
通过 source ./func_lib.sh 加载函数库,调用其中的函数实现计算:
#!/bin/bash
# 调用函数库的计算器脚本
# 加载函数库(需指定正确路径,若在同一目录可写相对路径)
source ./func_lib.sh
# 交互式接收用户输入
read -p "请输入第一个整数:" n1
read -p "请输入第二个整数:" n2
# 调用函数库中的函数,获取计算结果
sum=$(add $n1 $n2)
diff=$(subtract $n1 $n2)
product=$(multiply $n1 $n2)
quotient=$(divide $n1 $n2)
# 输出结果
echo -e "\n===== 计算结果 ====="
echo "两数之和:$sum"
echo "两数之差:$diff"
echo "两数之积:$product"
echo "两数之商:$quotient"
步骤 3:执行脚本并验证
- 确保 func_lib.sh 和 calc.sh 在同一目录;
- 赋予执行权限:chmod +x func_lib.sh calc.sh;
- 执行脚本:./calc.sh,输出示例:
请输入第一个整数:10
请输入第二个整数:3
===== 计算结果 =====
两数之和:13
两数之差:7
两数之积:30
两数之商:3
4.若输入分母为 0(如第二个数输入 0),会提示错误:
请输入第一个整数:10
请输入第二个整数:0
===== 计算结果 =====
两数之和:10
两数之差:10
两数之积:0
两数之商:错误:除法分母不能为 0
三、循环与函数的常见问题与注意事项
1. 循环相关注意事项
(1)while 循环的 “无限循环” 风险
- 若条件表达式恒为真(如 while true),需确保循环体内有 exit 或 break 退出逻辑,避免脚本卡死:
# 安全的无限循环(有退出条件)
while true; do
read -p "输入 q 退出:" cmd
if [ "$cmd" = "q" ]; then
break # 输入 q 时退出循环
fi
done
2. 函数相关注意事项
(1)局部变量的 “声明顺序”
- local 声明必须在变量使用前,否则变量会先被识别为全局变量,再转为局部变量,导致逻辑错误:
# 错误:先使用 m,再声明 local,此时 m 已被视为全局变量
sum() {
m=10 # 全局变量
local m # 后续修改 m 仅影响局部,但前一行已污染全局
m=20
echo $m
}
# 正确:先声明 local,再赋值使用
sum() {
local m
m=20
echo $m
}
(2)函数库的 “路径问题”
- 加载函数库时,若使用相对路径(如 ./func_lib.sh),需确保执行脚本时的 “当前工作目录” 正确;若函数库路径固定,建议用绝对路径(如 /root/func_lib.sh):
# 推荐:用绝对路径加载,避免目录切换导致找不到函数库
. /root/func_lib.sh
(3)函数返回值的 “获取时机”
- 用 $? 获取 return 的返回值时,需 “立即获取”,因为 $? 仅保存上一条命令的退出码,后续命令会覆盖它:
function sl {
read -p "请输入一个整数:" n
return $[ $n*2 ]
}
# 错误做法: echo 命令会覆盖 $? 的值
sl
echo "计算完成"
echo $?
# 输出 echo 命令的退出码(0),而非 sl 函数的返回值
# 正确做法:立即获取返回值
sl
result=$?
echo "返回值:$result" # 输入5 输出 10(正确)
四、总结
Shell 脚本的循环与函数是实现 “自动化” 和 “复用性” 的核心,需掌握以下关键点:
1. 循环:选对场景,控制流程
- for 循环:适合已知范围(如遍历列表、数字序列),嵌套可实现复杂逻辑(如九九乘法表);
- while 循环:适合条件未知(如等待用户输入、猜数字游戏),需配合退出逻辑;
- until 循环:与 while 相反,适合 “直到条件满足才停止” 的场景(如累加至目标值);
- break/continue:灵活控制循环,避免无效迭代。
2. 函数:封装逻辑,提升效率
- 定义格式:推荐省略 function,用 函数名() { ... },简洁且兼容标准;
- 返回值:小整数用 return(0~255),大值 / 字符串用 echo + 命令替换;
- 变量作用域:用 local 限定函数内变量,避免污染全局;
- 函数库:集中管理通用函数,通过 source 加载,减少重复代码。
通过 “循环控制流程 + 函数封装逻辑” 的组合,可编写结构清晰、可维护、可复用的 Shell 脚本,满足日常运维、自动化任务等需求。
更多推荐

所有评论(0)