Shell编程之函数与数组
目录
一、Shell 函数:代码组织与复用的核心
(一)函数基础:定义与调用
1. 函数定义语法
2. 函数调用方式
3. 示例:两数求和函数
(二)函数变量作用域:全局与局部变量
1. local关键字用法
2. 示例:全局与局部变量对比
(三)函数参数:传递与接收
1. 参数传递语法
2. 示例:日志写入函数
(四)递归函数:自我调用的实现
1. 示例:递归遍历目录
(五)系统资源监控函数:实战案例
1. 需求:定期监控 CPU 和内存使用率,超过阈值时报警
二、Shell 数组:高效处理批量数据
(一)数组定义:四种常见方式
1. 直接赋值(连续下标)
2. 显式指定下标
3. 从变量列表初始化
4. 逐个元素赋值
(二)数组基本操作
1. 获取数组长度
2. 读取指定下标元素
3. 遍历数组元素
4. 数组切片:提取子数组
5. 元素替换与删除
(三)稀疏数组:非连续下标处理
三、Shell 脚本调试:定位问题的关键工具
(一)echo 命令:分段排查
(二)bash 调试参数:三种模式
1. -n:语法检查(不执行脚本)
2. -v:显示脚本内容后执行
3. -x:显示执行的每一条命令
示例:调试分数判断脚本
(三)set 命令:局部调试
四、总结
(一)函数
(二)数组
(三)调试
一、Shell 函数:代码组织与复用的核心
(一)函数基础:定义与调用
在 Shell 脚本中,函数是将重复或独立的代码块封装成可复用单元的关键工具。通过函数,可避免代码冗余,提升脚本的可读性和模块化程度。
1. 函数定义语法
[function] 函数名() {命令序列[return x] # 可选,返回0表示成功,非0表示错误
}
function
关键字可选,省略后直接以函数名加括号定义。- 大括号
{}
内为函数体,包含具体执行的命令。 return
语句用于退出函数并返回状态码(0-255),省略时默认返回最后一条命令的执行状态。
2. 函数调用方式
函数名 [参数1] [参数2] ... # 直接使用函数名加参数列表调用
3. 示例:两数求和函数
脚本文件:sum.sh
#!/bin/bash
# 定义求和函数
sum() {echo "请输入第一个数:"read num1# 检查输入是否为整数if ! [[ $num1 =~ ^[0-9]+$ ]]; thenecho "错误:第一个数必须是整数!"return 1 # 返回错误码1fiecho "请输入第二个数:"read num2if ! [[ $num2 =~ ^[0-9]+$ ]]; thenecho "错误:第二个数必须是整数!"return 1firesult=$((num1 + num2))echo "两数之和为:$result"
}# 调用函数
sum
执行步骤:
chmod +x sum.sh # 赋予执行权限
./sum.sh # 运行脚本
输出结果:
请输入第一个数:
2
请输入第二个数:
3
两数之和为:5
(二)函数变量作用域:全局与局部变量
Shell 函数在当前 Shell 环境中执行,未声明的变量默认是全局变量,可被函数内外访问。若需限制变量仅在函数内有效,需使用local
关键字。
1. local
关键字用法
函数名() {local 变量名 # 声明局部变量,仅在函数内可见变量名=值
}
2. 示例:全局与局部变量对比
脚本文件:fun_scope.sh
#!/bin/bash
i=9 # 全局变量myfun() {local i=8 # 局部变量,与全局变量i隔离echo "函数内i:$i"
}myfun # 调用函数
echo "函数外i:$i" # 输出全局变量i
执行结果:
函数内i:8
函数外i:9
- 函数内通过
local
声明的i
为局部变量,修改不影响全局变量。 - 未声明
local
的变量在函数内修改会影响全局作用域。
(三)函数参数:传递与接收
函数可接收外部传递的参数,通过$1
、$2
...${10}
(第 10 个及之后参数需加花括号)获取,类似脚本的位置参数。
1. 参数传递语法
函数名 参数1 参数2 参数3 # 调用时传递参数
2. 示例:日志写入函数
脚本文件:write_log.sh
#!/bin/bash
mydir="/data"
outfile="${mydir}/my.log"
mkdir -p "$mydir" # 创建日志目录(若不存在)# 定义日志写入函数:第一个参数为文件路径,第二个为日志内容
appendfile() {echo "$2" >> "$1" # 将日志内容追加到文件
}# 调用函数,传递参数
appendfile "$outfile" "第一条日志:$(date)"
appendfile "$outfile" "第二条日志:Shell函数参数示例"
执行步骤:
chmod +x write_log.sh
./write_log.sh
cat /data/my.log # 查看日志内容
输出结果:
第一条日志:2025年4月15日 星期二 15:30:00
第二条日志:Shell函数参数示例
(四)递归函数:自我调用的实现
递归函数指函数在执行过程中调用自身,常用于遍历目录、斐波那契数列等场景。
1. 示例:递归遍历目录
脚本文件:fun_recursion.sh
#!/bin/bash
# 定义递归函数:遍历指定目录下的所有文件和子目录
traverse_directory() {local dir=$1 # 当前目录路径for item in "$dir"/*; do # 遍历当前目录下的所有项目if [ -d "$item" ]; then # 如果是目录echo "目录:$item"traverse_directory "$item" # 递归调用自身,遍历子目录elif [ -f "$item" ]; then # 如果是文件echo "文件:$item"fidone
}# 从当前目录开始遍历
traverse_directory "."
执行结果:
目录:./docs
文件:./docs/README.md
目录:./scripts
文件:./scripts/sum.sh
文件:./scripts/fun_scope.sh
- 通过
for
循环遍历目录项,递归处理子目录,实现层级遍历。 - 注意递归终止条件:当遍历到文件时不再递归,避免无限循环。
(五)系统资源监控函数:实战案例
1. 需求:定期监控 CPU 和内存使用率,超过阈值时报警
脚本文件:jiankong.sh
#!/bin/bash
# 发送报警信息的函数
send_alert() {local message=$1echo "ALERT: $message" # 实际可扩展为发送邮件/短信
}# 监控系统资源的函数:参数依次为CPU阈值、内存阈值、监控间隔(秒)
monitor_system_resources() {local cpu_threshold=$1local mem_threshold=$2local interval=$3while true; do# 获取CPU使用率(用户+系统占用率)cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{printf "%.2f", $2 + $4}')# 获取内存使用率(已用内存/总内存*100)mem_usage=$(free | awk '/Mem/ {printf "%.2f", $3/$2 * 100}')# 比较CPU使用率if (( $(echo "$cpu_usage > $cpu_threshold" | bc -l) )); thensend_alert "CPU使用率 $cpu_usage% 超过阈值 $cpu_threshold%!"fi# 比较内存使用率if (( $(echo "$mem_usage > $mem_threshold" | bc -l) )); thensend_alert "内存使用率 $mem_usage% 超过阈值 $mem_threshold%!"fisleep "$interval" # 等待指定间隔done
}# 启动监控:CPU阈值80%,内存阈值85%,间隔5秒
monitor_system_resources 80 85 5
关键技术点:
top -bn1
:非交互模式获取单次系统状态,避免交互式界面。bc -l
:处理浮点数比较,需安装bc
工具(yum install bc
或apt-get install bc
)。while true
:无限循环实现持续监控,可通过Ctrl+C
终止。
二、Shell 数组:高效处理批量数据
Shell 数组是存储多个数据的集合,支持一维数组,下标从 0 开始,元素用空格分隔。
(一)数组定义:四种常见方式
1. 直接赋值(连续下标)
数组名=(value0 value1 value2 ...)
# 示例
arr1=(1 2 3 4 5) # 下标0-4,元素1-5
2. 显式指定下标
数组名=([0]=value [1]=value [2]=value ...)
# 示例
arr2=([0]="apple" [2]="banana" [3]="orange") # 下标0、2、3有值,1为空
3. 从变量列表初始化
列表="value0 value1 value2"
数组名=($列表)
# 示例
list="hello world shell"
arr3=($list) # arr3=(hello world shell)
4. 逐个元素赋值
数组名[0]="value"
数组名[1]="value"
# 示例
arr4[0]="red"
arr4[1]="green"
arr4[2]="blue"
(二)数组基本操作
1. 获取数组长度
${#数组名[@]} 或 ${#数组名[*]} # 两种语法等价
# 示例
arr=(1 2 3 4 5)
echo "数组长度:${#arr[@]}" # 输出5
2. 读取指定下标元素
${数组名[下标]} # 下标从0开始,负数表示从末尾倒数(如-1为最后一个元素)
# 示例
echo "第三个元素:${arr[2]}" # 输出3
echo "最后一个元素:${arr[-1]}" # 输出5(Bash 4.0+支持负下标)
3. 遍历数组元素
for 变量 in ${数组名[@]}; do操作
done
# 示例脚本:array_traverse.sh
#!/bin/bash
arr=(1 2 3 4 5)
for value in "${arr[@]}"; do # 加引号避免空格分割问题echo "元素:$value"
done
执行结果:
元素:1
元素:2
元素:3
元素:4
元素:5
4. 数组切片:提取子数组
语法:${数组名[@]:起始下标:长度}
arr=(1 2 3 4 5 6)
echo "前两个元素:${arr[@]:0:2}" # 输出1 2
echo "从下标2开始取3个元素:${arr[@]:2:3}" # 输出3 4 5
5. 元素替换与删除
- 替换(不修改原数组):
echo "${arr[@]/旧值/新值}" # 替换第一个匹配项 echo "${arr[@]//旧值/新值}" # 替换所有匹配项
- 修改原数组:
arr=(${arr[@]/旧值/新值}) # 重新赋值
- 删除元素:
unset arr[下标] # 删除指定下标元素 unset arr # 删除整个数组
示例:
arr=(1 2 3 4 3 5)
echo "替换第一个3为6:${arr[@]/3/6}" # 输出1 2 6 4 3 5
echo "替换所有3为6:${arr[@]//3/6}" # 输出1 2 6 4 6 5
arr=(${arr[@]//3/6}) # 原数组变为(1 2 6 4 6 5)
unset arr[2] # 删除下标2的元素(6)
echo "删除后数组:${arr[@]}" # 输出1 2 4 6 5
(三)稀疏数组:非连续下标处理
Shell 允许数组下标不连续,未赋值的下标默认为空。
arr=([0]="a" [3]="b") # 下标0和3有值,1、2为空
echo "数组所有下标:${!arr[@]}" # 输出0 3(${!数组名[@]}获取所有下标)
echo "下标1的值:${arr[1]}" # 输出空
三、Shell 脚本调试:定位问题的关键工具
编写脚本时难免出错,调试工具可帮助快速定位语法或逻辑错误。
(一)echo 命令:分段排查
在可能出错的代码段插入echo
语句,输出变量值或执行状态。
#!/bin/bash
read -p "输入数字:" num
echo "输入的数字是:$num" # 调试用,检查输入是否正确
if [ $num -gt 10 ]; thenecho "大于10"
elseecho "小于等于10"
fi
(二)bash 调试参数:三种模式
通过sh
或bash
命令带参数执行脚本,开启调试模式。
1. -n
:语法检查(不执行脚本)
sh -n script.sh # 仅检查语法,不报逻辑错误
- 输出:若有语法错误(如未闭合的引号、缺少
fi
),会提示具体位置。
2. -v
:显示脚本内容后执行
sh -v script.sh # 先打印脚本每一行,再执行
- 适合查看脚本是否按预期读取文件或变量。
3. -x
:显示执行的每一条命令
sh -x script.sh # 打印每条执行的命令及其参数
- 输出带
+
前缀的命令,便于追踪逻辑错误。
示例:调试分数判断脚本
脚本文件:test.sh
#!/bin/bash
read -p "请输入分数(0-100):" GRADE
if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ]; thenecho "优秀"
elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ]; thenecho "合格"
elseecho "不合格"
fi
调试执行:
sh -x test.sh # 输出每条命令的执行过程
输出片段:
+ read -p '请输入分数(0-100):' GRADE
请输入分数(0-100):75
+ [ 75 -ge 85 ]
+ [ 75 -ge 70 ] && [ 75 -le 84 ]
+ echo 合格
合格
(三)set 命令:局部调试
在脚本中使用set -x
开启调试,set +x
关闭,仅调试部分代码。
#!/bin/bash
set -x # 开启调试
echo "开始处理"
read -p "输入内容:" input
set +x # 关闭调试
echo "输入的内容是:$input"
执行结果:
+ echo 开始处理
开始处理
+ read -p 输入内容: input
输入内容: hello
输入的内容是: hello
四、总结
(一)函数
- 复用性:避免重复代码,提升开发效率。
- 可读性:将复杂逻辑拆分为独立函数,结构清晰。
- 灵活性:支持参数传递、递归调用,适应多样化场景。
(二)数组
- 高效存储:无需预先定义长度,动态扩展。
- 丰富操作:切片、替换、删除等功能满足数据处理需求。
- 遍历便利:结合循环快速处理数组元素。
(三)调试
- 语法检查:
-n
参数快速定位语法错误。 - 执行追踪:
-x
参数显示命令执行流程,排查逻辑问题。 - 分段调试:
set
命令灵活控制调试范围,避免全脚本输出冗余信息。