一、基本运算符
Shell支持多种运算符,主要包括:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
- 位运算符
- 赋值运算符
1.1 算术运算符
1.1.1 基本算术运算
原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用,主要用于数字间的算术运算。常用的算术运算符有:+(加)、-(减)、*(乘)、/(除)、%(取余)、=(赋值)、==(等于)、!=(不等于)。需要注意以下几点:
- 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2
- 完整的表达式要被 `` 包含,注意这个字符不是常用的单引号
- 乘号
*前边必须加反斜杠\才能实现乘法运算。
bash
# 方法1:使用 expr(旧方法,兼容性好)
a=10
b=3
echo `expr $a + $b` # 13
echo `expr $a - $b` # 7
echo `expr $a \* $b` # 30(注意:*需要转义)
echo `expr $a / $b` # 3(整数除法)
echo `expr $a % $b` # 1(取余)
# 方法2:使用 $(( ))(推荐)
echo $((a + b)) # 13
echo $((a - b)) # 7
echo $((a * b)) # 30(无需转义)
echo $((a / b)) # 3
echo $((a % b)) # 1
echo $((a ** b)) # 1000(幂运算,bash 4.0+)
# 方法3:使用 let 命令
let "c = a + b"
echo $c # 13
# 方法4:使用 bc(支持小数)
echo "scale=2; $a / $b" | bc # 3.33
echo "$a + $b" | bc # 13
1.1.2 自增自减运算
bash
x=5
# 后自增
echo $((x++)) # 5,然后x=6
echo $x # 6
# 前自增
echo $((++x)) # 7,x=7
# 自减
echo $((x--)) # 7,然后x=6
echo $((--x)) # 5,x=5
# 简写运算
x=10
((x += 3)) # x=13
((x -= 2)) # x=11
((x *= 2)) # x=22
((x /= 2)) # x=11
((x %= 4)) # x=3
((x **= 2)) # x=9
1.1.3 浮点数运算
bash
# 使用 bc 命令
a=10.5
b=3.2
echo "$a + $b" | bc # 13.7
echo "$a - $b" | bc # 7.3
echo "$a * $b" | bc # 33.6
echo "scale=2; $a / $b" | bc # 3.28(scale设置小数位数)
echo "scale=2; sqrt($a)" | bc -l # 3.24(平方根,-l加载数学库)
# 使用 awk
echo "$a $b" | awk '{print $1 + $2}' # 13.7
echo "$a $b" | awk '{print $1 * $2}' # 33.6
1.2 关系运算符
关系运算符只支持整数,不支持小数和字符串,除非字符串的值是数字,主要用于两个整数大小的比较。常用的关系运算符有:-eq(等于)、-ne(不等于)、-gt(大于)、-lt(小于)、-ge(大于等于)、-le(小于等于)、==(等于)、!=(不等于)、>(大于)、<(小于)、>=(大于等于)、<=(小于等于)
1.2.1 数值比较
bash
a=2
b=5
注意:
1.使用[]或者[[]]时,变量与[]或者[[]]之间必须要有空格隔开。
if [ $a -lt $b ]
if [[ $a -lt $b ]]
2.使用(())时,变量与(())之间不需要空格隔开,且不能使用英文表示的运算符
if (($a<$b))
if (($a>=$b))
3.使用[]且不使用英文代表的运算符时,>和<前面需要使用转义符\,防止当作重定向符使用,使用[[]]和(())不需要转义
if [ $a \< $b ]
if [[ $a < $b ]]
if (($a<$b))
4.使用英文表示的关系运算符时,运算符与变量之间需要空格隔开,使用符号表示的关系运算符时,
运算符与变量之间最好不要使用空格隔开
if [[ $a <= $b ]] ## 会报错
if [[ $a<=$b ]]
if (($a<=$b))
1.2.2 字符串比较
bash
str1="hello"
str2="world"
# 使用 test 或 [ ]
if [ "$str1" = "$str2" ]; then echo "相等"; fi # =:等于
if [ "$str1" != "$str2" ]; then echo "不等"; fi # !=:不等于
if [ -z "$str1" ]; then echo "空字符串"; fi # -z:长度为0
if [ -n "$str1" ]; then echo "非空字符串"; fi # -n:长度不为0
# 使用 [[ ]](扩展测试)
if [[ "$str1" == "$str2" ]]; then echo "相等"; fi
if [[ "$str1" != "$str2" ]]; then echo "不等"; fi
if [[ "$str1" < "$str2" ]]; then echo "字典序小于"; fi
if [[ "$str1" > "$str2" ]]; then echo "字典序大于"; fi
if [[ "$str1" =~ ^h ]]; then echo "以h开头"; fi # =~:正则匹配
1.3 布尔运算符
1.3.1 逻辑运算符
bash
a=10
b=20
# 逻辑与
if [ $a -lt 15 -a $b -gt 15 ]; then echo "条件成立"; fi # -a:AND
if [[ $a -lt 15 && $b -gt 15 ]]; then echo "条件成立"; fi # &&:AND
# 逻辑或
if [ $a -gt 15 -o $b -gt 15 ]; then echo "条件成立"; fi # -o:OR
if [[ $a -gt 15 || $b -gt 15 ]]; then echo "条件成立"; fi # ||:OR
# 逻辑非
if [ ! $a -gt 15 ]; then echo "a不大于15"; fi # !:NOT
# 示例:组合使用
if [[ $a -gt 5 && $a -lt 15 ]] || [[ $b -gt 15 ]]; then
echo "复杂条件成立"
fi
注意:
1.使用!运算符时需要与[]之间保持一个空格隔开。
2.!可以在[ ]、[[ ]]中使用,但不可以在(( ))中使用。
3.使用-a或者-o时,必须使用[]。
1.3.2 短路运算
bash
# && 短路:前一个成功才执行后一个
[ -f "/etc/passwd" ] && echo "文件存在" # 文件存在才打印
# || 短路:前一个失败才执行后一个
[ -f "/nonexistent" ] || echo "文件不存在" # 文件不存在才打印
# 组合使用
command1 && command2 && command3 # 全部成功执行
command1 || command2 || command3 # 有一个成功就停止
# 实用示例
mkdir -p backup && tar -czf backup/data.tar.gz data/ || echo "备份失败"
1.4 字符串运算符
主要用于比较字符串的内容。常用的字符串有:=(相等)、!=(不相等)、-z(检测字符串长度是否为0)、-n(检测字符串长度是否不为 0)、$(检测字符串是否不为空)
1.4.1 基本字符串操作
bash
str="Hello World"
# 判断是否为空
if [ -z "$str" ]; then echo "空"; fi
if [ -n "$str" ]; then echo "非空"; fi
# 字符串相等
if [ "$str" = "Hello World" ]; then echo "相等"; fi
if [ "$str" != "Goodbye" ]; then echo "不相等"; fi
# 字符串比较(按字典序)
if [[ "$str" < "Zebra" ]]; then echo "在Zebra之前"; fi
if [[ "$str" > "Apple" ]]; then echo "在Apple之后"; fi
1.4.2 字符串长度和子串
bash
str="Hello World"
# 获取长度
length=${#str}
echo "长度: $length" # 11
# 提取子串
echo ${str:6} # World(从索引6开始)
echo ${str:6:5} # World(从6开始取5个字符)
echo ${str: -5} # World(从倒数第5开始)
1.5 文件测试运算符
主要用于检测 Unix 文件的各种属性。
1.5.1 文件存在性和类型
bash
file="/root/test.sh"
# -b 检测文件是否是块设备文件,如果是,则返回 true。
if [ -b $file ]
# -c 检测文件是否是字符设备文件,如果是,则返回 true。
if [ -c $file ]
# -d 检测文件是否是目录,如果是,则返回 true。
if [ -d $file ]
# -f 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。
if [ -f $file ]
# -r 检测文件是否可读,如果是,则返回 true。
if [ -r $file ]
# -w 检测文件是否可写,如果是,则返回 true。
if [ -w $file ]
# -x 检测文件是否可执行,如果是,则返回 true。
if [ -x $file ]
# -s 检测文件是否为空(文件大小是否大于0),不为空返回 true。
if [ -s $file ]
# -e 检测文件(包括目录)是否存在,如果是,则返回 true。
if [ -e $file ]
1.5.2 文件权限检查
bash
# 权限检查
if [ -r "$file" ]; then echo "可读"; fi # -r:可读
if [ -w "$file" ]; then echo "可写"; fi # -w:可写
if [ -x "$file" ]; then echo "可执行"; fi # -x:可执行
if [ -u "$file" ]; then echo "设置了SUID"; fi # -u:SUID位
if [ -g "$file" ]; then echo "设置了SGID"; fi # -g:SGID位
if [ -k "$file" ]; then echo "设置了粘滞位"; fi # -k:粘滞位
1.5.3 文件属性检查
bash
# 其他属性
if [ -s "$file" ]; then echo "非空文件"; fi # -s:文件大小>0
if [ -N "$file" ]; then echo "自上次读取后被修改"; fi # -N:修改过
# 文件比较
file1="/path/file1"
file2="/path/file2"
if [ "$file1" -nt "$file2" ]; then echo "file1较新"; fi # -nt:newer than
if [ "$file1" -ot "$file2" ]; then echo "file1较旧"; fi # -ot:older than
if [ "$file1" -ef "$file2" ]; then echo "同一文件"; fi # -ef:相同inode
# 检查文件描述符
if [ -t 0 ]; then echo "标准输入是终端"; fi # -t fd:是终端
1.6 位运算符
bash
a=60 # 60 = 0011 1100
b=13 # 13 = 0000 1101
# 位运算
echo $((a & b)) # 12: 按位与 0011 1100 & 0000 1101 = 0000 1100
echo $((a | b)) # 61: 按位或 0011 1100 | 0000 1101 = 0011 1101
echo $((a ^ b)) # 49: 按位异或 0011 1100 ^ 0000 1101 = 0011 0001
echo $((~a)) # -61: 按位取反(注意:使用补码)
echo $((a << 2)) # 240: 左移2位 0011 1100 << 2 = 1111 0000
echo $((a >> 2)) # 15: 右移2位 0011 1100 >> 2 = 0000 1111
1.7 赋值运算符
bash
# 基本赋值
a=10
b=$a
echo "b = $b" # 10
# 复合赋值
x=5
((x += 3)) # x = x + 3 = 8
((x -= 2)) # x = 8 - 2 = 6
((x *= 4)) # x = 6 * 4 = 24
((x /= 3)) # x = 24 / 3 = 8
((x %= 5)) # x = 8 % 5 = 3
((x **= 2)) # x = 3^2 = 9
# 位运算赋值
y=15 # 15 = 1111
((y &= 7)) # y = 15 & 7 = 7 (1111 & 0111 = 0111)
((y |= 8)) # y = 7 | 8 = 15 (0111 | 1000 = 1111)
((y ^= 5)) # y = 15 ^ 5 = 10 (1111 ^ 0101 = 1010)
((y <<= 1)) # y = 10 << 1 = 20
((y >>= 2)) # y = 20 >> 2 = 5
1.8 运算符优先级
1.8.1 优先级表(从高到低)
bash
# 最高优先级
1. () # 分组
2. ++ -- # 自增自减
3. ** # 幂运算
4. * / % # 乘除取余
5. + - # 加减
6. << >> # 位移
7. < <= > >= # 比较
8. == != =~ # 相等匹配
9. & # 按位与
10. ^ # 按位异或
11. | # 按位或
12. && # 逻辑与
13. || # 逻辑或
14. = += -= *= /= %= **= # 赋值
15. &= ^= |= <<= >>= # 位运算赋值
# 最低优先级
1.8.2 优先级示例
bash
result=$(( 2 + 3 * 4 )) # 14(先乘后加)
result=$(( (2 + 3) * 4 )) # 20(括号优先)
result=$(( 2 ** 3 * 2 )) # 16(先幂运算后乘法)
result=$(( 5 > 3 && 2 < 4 )) # 1(true)
二、流程控制
2.1 条件判断(if语句)
2.1.1 基本if语句
bash
# 单条件判断
if [ condition ]; then
commands
fi
# 示例:判断文件是否存在
if [ -f "/etc/passwd" ]; then
echo "文件存在"
fi
2.1.2 if-else语句
bash
if [ condition ]; then
commands1
else
commands2
fi
# 示例:检查用户
if [ "$(whoami)" = "root" ]; then
echo "您是root用户"
else
echo "您不是root用户"
fi
2.1.3 if-elif-else语句
bash
if [ condition1 ]; then
commands1
elif [ condition2 ]; then
commands2
elif [ condition3 ]; then
commands3
else
default_commands
fi
# 示例:成绩评级
score=85
if [ $score -ge 90 ]; then
echo "优秀"
elif [ $score -ge 80 ]; then
echo "良好"
elif [ $score -ge 60 ]; then
echo "及格"
else
echo "不及格"
fi
2.1.4 嵌套if语句
bash
# 检查用户和文件权限
user="admin"
file="/etc/config.conf"
if [ "$user" = "admin" ]; then
if [ -f "$file" ]; then
if [ -r "$file" ]; then
echo "管理员可以读取配置文件"
else
echo "配置文件不可读"
fi
else
echo "配置文件不存在"
fi
else
echo "权限不足"
fi
2.2 case语句
2.2.1 基本语法
bash
case expression in
pattern1)
commands1
;;
pattern2)
commands2
;;
pattern3|pattern4) # 多个模式
commands3
;;
*)
default_commands
;;
esac
# 示例:菜单选择
read -p "请输入选项 (start|stop|restart|status): " choice
case $choice in
start)
echo "启动服务..."
systemctl start nginx
;;
stop)
echo "停止服务..."
systemctl stop nginx
;;
restart)
echo "重启服务..."
systemctl restart nginx
;;
status)
echo "服务状态..."
systemctl status nginx
;;
*)
echo "无效选项"
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac
1.expression 既可以是一个变量、一个数字、一个字符串,还可以是一个数学计算表达式,或者是命令的执行结果,
只要能够得到 expression 的值就可以。
2.pattern 可以是一个数字、一个字符串,甚至是一个简单的正则表达式。
3.case 会将 expression 的值与 pattern1、pattern2、pattern3 逐个进行匹配,匹配成功就执行后面对应的所有语句
(该语句可以有一条,也可以有多条),直到遇见双分号 ;; 结束,跳出整个 case 语句。
4. case in 类似Java的 switch ... case 语句,而 ;; 类似其中的break跳出循环的作用,*) 类似其中的 default起到托底作用。
5.最后的 *) 部分可以没有,最后的 *) 部分中的 ;; 可以写也可以不写,但是其他分支的 ;; 必须要写。
2.2.2 模式匹配
bash
# 通配符模式
read -p "输入文件名: " filename
case $filename in
*.txt)
echo "文本文件"
;;
*.jpg|*.jpeg|*.png|*.gif)
echo "图片文件"
;;
*.tar.gz|*.tgz)
echo "压缩文件"
;;
*)
echo "未知文件类型"
;;
esac
# 正则表达式模式(Bash 4.0+)
read -p "输入IP地址: " ip
case $ip in
[0-9]*.*[0-9]*.*[0-9]*.*[0-9]*)
echo "可能是IP地址"
;;
*)
echo "不是IP地址格式"
;;
esac
2.3 循环结构
2.3.1 for循环
bash
# 1.for 循环中的 exp1(初始化语句)、exp2(判断条件)和 exp3(自增或自减)都是可选项
# 都可以省略(但分号;必须保留)
for((exp1; exp2; exp3))
do
...
done
# 例1. 计算从 1 加到 100 的和
for ((i=1; i<=100; i++))
do
((sum += i))
done
# 2.for in 循环,每次循环都会从list中取出一个值赋给变量 num
# 然后进入循环体(do 和 done 之间的部分)
# 执行循环体中的操作。直到取完list中的所有值,循环就结束了
# 其中list的形式有多种,可以直接给出具体的一系列值
# 也可以给出一个范围,还可以使用命令产生的结果,甚至使用通配符。
for num in list
do
...
done
# 例2. 计算从 1 加到 100 的和
sum=0
for n in {1..100}
do
((sum+=n))
done
# 或者写成一行
for n in {1..100}; do ((sum+=n)); done;
2.3.2 while循环
bash
# while 循环是 Shell 脚本中最简单的一种循环,当条件满足时,
# while 重复地执行一组语句,当条件不满足时,就退出 while 循环。
while condition
do
command
done
# 例1.计算从 1 加到 100 的和
i=1
sum=0
while ((i <= 100))
do
((sum += i))
((i++))
done
# 例2.实现无限循环
while true
do
command
done
或者
while :
do
command
done
2.3.3 until循环
bash
# until循环与 while 循环在处理方式上刚好相反,循环执行一系列命令直至条件为 true 时停止
until condition
do
command
done
# 例1.计算从 1 加到 100 的和
i=1
sum=0
until ((i > 100))
do
((sum += i))
((i++))
done
2.4 循环控制语句
Shell 也是通过 break 和 continue 两个关键字来跳出循环,和Java中的功能是完全一样的,两者的区别跟Java的也是一样的。break 和 continue 的区别在于 break 是跳出整个循环,而 continue 只是跳出当前循环。
2.4.1 break语句
bash
# 跳出当前循环
for i in {1..10}; do
if [ $i -eq 5 ]; then
echo "遇到5,跳出循环"
break
fi
echo "当前值: $i"
done
# 跳出多层循环
for i in {1..3}; do
echo "外层循环: $i"
for j in {1..3}; do
if [ $j -eq 2 ]; then
echo "内层循环遇到2,跳出所有循环"
break 2 # 2表示跳出两层循环
fi
echo " 内层循环: $j"
done
done
2.4.2 continue语句
bash
# 跳过本次循环,继续下一次
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # 跳过偶数
fi
echo "奇数: $i"
done
# 跳过内层循环
for i in {1..3}; do
echo "外层: $i"
for j in {1..3}; do
if [ $j -eq 2 ]; then
continue 2 # 继续外层循环的下一次
fi
echo " 内层: $j"
done
done
2.5 select语句(菜单选择)
bash
# 创建交互式菜单
PS3="请选择操作 (1-4): "
options=("查看状态" "启动服务" "停止服务" "退出")
select opt in "${options[@]}"; do
case $REPLY in # $REPLY是用户输入的数字
1)
echo "查看服务状态..."
systemctl status nginx
;;
2)
echo "启动服务..."
systemctl start nginx
;;
3)
echo "停止服务..."
systemctl stop nginx
;;
4)
echo "退出"
break
;;
*)
echo "无效选项"
;;
esac
done
# 或使用case $opt
select opt in "选项1" "选项2" "退出"; do
case $opt in
"选项1")
echo "执行选项1"
;;
"选项2")
echo "执行选项2"
;;
"退出")
break
;;
*)
echo "无效选项"
;;
esac
done
2.6 函数中的流程控制
bash
# 函数中的条件判断
check_file() {
local file=$1
if [ ! -e "$file" ]; then
echo "错误:文件不存在" >&2
return 1
fi
if [ -d "$file" ]; then
echo "这是一个目录"
elif [ -f "$file" ]; then
echo "这是一个普通文件"
if [ -x "$file" ]; then
echo "并且可以执行"
fi
fi
return 0
}
# 调用函数
check_file "/bin/bash"
# 函数中的循环
process_files() {
local dir=$1
for file in "$dir"/*; do
if [ -f "$file" ]; then
echo "处理文件: $(basename "$file")"
# 处理逻辑...
fi
done
}
2.7 性能优化
bash
# 1. 避免在循环中调用外部命令
# 较差:
for user in $(cat /etc/passwd | cut -d: -f1); do
echo "$user"
done
# 较好:
while IFS=: read -r username _; do
echo "$username"
done < /etc/passwd
# 2. 使用内置字符串操作代替外部命令
# 较差:
len=$(echo "$str" | wc -c)
# 较好:
len=${#str}
# 3. 批量操作代替逐个操作
# 较差:
for file in *.txt; do
cp "$file" "$file.bak"
done
# 较好:
cp *.txt *.txt.bak 2>/dev/null || true
三、Shell 函数
3.1 函数的基本概念
3.1.1 什么是Shell函数?
- 函数是一段可以重复调用的代码块
- 提高代码复用性和可维护性
- 使脚本结构更清晰
- 支持局部变量和参数传递
3.1.2 函数定义方式
bash
# 方式1:传统C语言风格
function_name() {
commands
return value
}
# 方式2:使用function关键字
function function_name {
commands
return value
}
# 方式3:function关键字带括号
function function_name() {
commands
return value
}
# 示例:定义简单函数
say_hello() {
echo "Hello, World!"
}
# 调用函数
say_hello
# 注意:
1.return value 表示函数的返回值,value 的值是 (0-255之间的任意数,当然也可以不写 return value,
这时会将最后一条命令运行结果,作为返回值。
2.方法的()中没有参数的定义,这个跟Java的方法参数定义不太一样,shell的参数是在调用函数的时候直接写在函数后面的。
3.2 函数参数
3.2.1 位置参数
bash
# 定义带参数的函数
greet() {
local name=$1
local greeting=${2:-"Hello"} # 默认值
echo "$greeting, $name!"
}
# 调用函数
greet "Alice" # Hello, Alice!
greet "Bob" "Hi" # Hi, Bob!
greet # Hello, ! (name为空)
# 访问所有参数
show_args() {
echo "参数个数: $#"
echo "所有参数: $@"
echo "第一个参数: $1"
echo "第二个参数: $2"
echo "最后一个参数: ${@: -1}"
}
show_args one two three four
3.2.2 特殊参数变量
bash
process_args() {
echo "脚本名: $0" # 脚本名称
echo "参数个数: $#" # 参数数量
echo "所有参数: $@" # 所有参数(可迭代)
echo "所有参数: $*" # 所有参数(单个字符串)
echo "上个命令退出状态: $?" # 函数返回值
echo "当前进程PID: $$" # 进程ID
echo "后台最后一个进程PID: $!" # 后台进程ID
# 遍历所有参数
for arg in "$@"; do
echo "参数: $arg"
done
}
process_args arg1 arg2 arg3
3.3 函数调用
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。函数时可以给它传递参数,也可以不传递。如果不传递参数,直接给出函数名字即可。如果传递参数,那么多个参数之间以空格分隔。
bash
## 定义一个函数
function name(){
echo "初始化的name为:TOM !"
echo "接收第一个name参数为 $1 !"
echo "接收第二个name参数为 $2 !"
echo "接收的参数总数有 $# 个!"
echo "作为一个字符串输出所有参数 $* !"
return $#
}
## 不传参调用函数
echo "=============first use with no param=============="
name
## 传参调用函数
echo "=============second use with param================"
name Tom Jack
## 输出函数返回结果
echo "the function result is:$?"

3.4 返回值处理
3.4.1 return语句
bash
# 使用return返回状态码(0-255)
check_file() {
if [ -f "$1" ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
check_file "/etc/passwd"
if [ $? -eq 0 ]; then
echo "文件存在"
else
echo "文件不存在"
fi
# 返回值范围限制
return_test() {
return 300 # 实际上会返回 300 % 256 = 44
}
return_test
echo "返回值: $?" # 44
3.4.2 使用echo返回值
bash
# 返回字符串或数字
get_date() {
date "+%Y-%m-%d %H:%M:%S"
}
current_time=$(get_date)
echo "当前时间: $current_time"
# 返回计算结果
add() {
local result=$(($1 + $2))
echo $result
}
sum=$(add 10 20)
echo "和: $sum"
# 返回多个值(使用数组)
get_user_info() {
local user=$1
local info=()
info[0]=$(id -u "$user")
info[1]=$(id -g "$user")
info[2]=$(grep "^$user:" /etc/passwd | cut -d: -f7)
echo "${info[@]}"
}
user_data=($(get_user_info "root"))
echo "UID: ${user_data[0]}"
echo "GID: ${user_data[1]}"
echo "Shell: ${user_data[2]}"
3.5 变量作用域
3.5.1 局部变量
bash
# 默认变量是全局的
global_var="I'm global"
test_scope() {
# 创建局部变量
local local_var="I'm local"
# 修改全局变量(不推荐)
global_var="Modified inside function"
echo "函数内 - 局部变量: $local_var"
echo "函数内 - 全局变量: $global_var"
}
test_scope
echo "函数外 - 全局变量: $global_var"
echo "函数外 - 局部变量: $local_var" # 空,因为局部变量已销毁
# 数组局部变量
array_test() {
local -a local_array=(1 2 3)
local_array[3]=4
echo "函数内数组: ${local_array[@]}"
}
array_test
echo "函数外访问: ${local_array[@]}" # 空
3.5.2 变量引用传递
bash
# 通过变量名引用修改外部变量
modify_var() {
local var_name=$1
local new_value=$2
eval "$var_name='$new_value'"
}
my_var="old value"
echo "修改前: $my_var"
modify_var "my_var" "new value"
echo "修改后: $my_var"
# 使用declare -n引用(Bash 4.3+)
swap() {
local -n a=$1
local -n b=$2
local temp=$a
a=$b
b=$temp
}
x=10
y=20
echo "交换前: x=$x, y=$y"
swap x y
echo "交换后: x=$x, y=$y"
3.6 递归函数
bash
# 计算阶乘
factorial() {
local n=$1
# 基本情况
if [ $n -le 1 ]; then
echo 1
return
fi
# 递归调用
local prev=$(factorial $((n-1)))
echo $((n * prev))
}
result=$(factorial 5)
echo "5! = $result" # 120
# 斐波那契数列
fibonacci() {
local n=$1
if [ $n -le 0 ]; then
echo 0
elif [ $n -eq 1 ]; then
echo 1
else
local a=$(fibonacci $((n-1)))
local b=$(fibonacci $((n-2)))
echo $((a + b))
fi
}
echo "斐波那契数列:"
for i in {0..10}; do
echo -n "$(fibonacci $i) "
done
echo
# 目录递归遍历
list_files() {
local dir=$1
local indent=${2:-""}
echo "${indent}📁 $(basename "$dir")/"
for item in "$dir"/*; do
if [ -d "$item" ]; then
list_files "$item" "$indent "
elif [ -f "$item" ]; then
echo "${indent} 📄 $(basename "$item")"
fi
done
}
list_files "/home/user"
3.7 高阶函数特性
3.7.1 函数作为参数
bash
# 回调函数示例
apply_to_array() {
local func=$1
shift
local array=("$@")
for element in "${array[@]}"; do
$func "$element"
done
}
# 定义处理函数
print_item() {
echo "处理: $1"
}
to_upper() {
echo "$1" | tr '[:lower:]' '[:upper:]'
}
# 使用回调
my_array=("apple" "banana" "cherry")
echo "原始数组:"
apply_to_array print_item "${my_array[@]}"
echo "转换为大写:"
apply_to_array to_upper "${my_array[@]}"
3.7.2 函数返回函数
bash
# 工厂函数
create_multiplier() {
local factor=$1
multiplier() {
local num=$1
echo $((num * factor))
}
# 返回函数名
echo "multiplier"
}
# 创建不同的乘数函数
double_func=$(create_multiplier 2)
triple_func=$(create_multiplier 3)
# 调用动态创建的函数
$double_func 10 # 20
$triple_func 10 # 30
3.8 错误处理和调试
3.8.1 错误处理函数
bash
#!/bin/bash
# 设置错误处理
set -e # 遇到错误退出
set -u # 使用未定义变量时报错
set -o pipefail # 管道中任意命令失败则整个失败
# 错误处理函数
error_exit() {
local error_msg="$1"
local exit_code=${2:-1}
echo "错误: $error_msg" >&2
exit $exit_code
}
# 清理函数
cleanup() {
echo "执行清理操作..."
# 删除临时文件等
rm -f /tmp/temp_*.$$
}
# 信号捕获
trap cleanup EXIT
trap 'error_exit "脚本被中断" 130' INT TERM
# 使用示例
process_data() {
local input_file="$1"
# 检查文件
if [ ! -f "$input_file" ]; then
error_exit "输入文件不存在: $input_file"
fi
# 处理文件
if ! grep -q "pattern" "$input_file"; then
error_exit "文件中未找到pattern"
fi
echo "处理成功"
}
3.8.2 函数调试
bash
#!/bin/bash
# 调试函数
debug() {
if [ "$DEBUG" = "true" ]; then
echo "[DEBUG] $1" >&2
fi
}
# 带调试的函数
complex_function() {
debug "开始执行 complex_function"
debug "参数: $@"
local step=1
debug "步骤 $step: 初始化"
# ... 代码 ...
((step++))
debug "步骤 $step: 处理数据"
# ... 代码 ...
debug "函数执行完成"
}
# 启用调试
export DEBUG=true
complex_function "test" "data"
# 使用set -x调试特定函数
debug_function() {
set -x # 开启调试
# 函数代码
local var="test"
echo "$var"
set +x # 关闭调试
}
3.9 性能优化技巧
3.9.1 避免在循环中定义函数
bash
# 错误:每次循环都重新定义函数
for i in {1..1000}; do
process() {
echo "处理 $i"
}
process
done
# 正确:在循环外定义一次
process() {
local i=$1
echo "处理 $i"
}
for i in {1..1000}; do
process "$i"
done
3.9.2 使用本地变量
bash
# 错误:使用全局变量
process_data() {
result="" # 全局变量
# ... 处理 ...
}
# 正确:使用本地变量
process_data() {
local result="" # 局部变量
# ... 处理 ...
}
3.9.3 批量处理
bash
# 较差:多次调用
for file in *.txt; do
process_file "$file"
done
# 较好:批量处理
process_files() {
local files=("$@")
# 批量处理逻辑
}
process_files *.txt
3.10 最佳实践总结
3.10.1 函数设计原则
bash
# 1. 单一职责原则
# 一个函数只做一件事
process_file() { ... } # 处理文件
validate_input() { ... } # 验证输入
generate_report() { ... } # 生成报告
# 2. 良好的命名
# 使用动词+名词,清晰表达功能
calculate_average() { ... }
send_notification() { ... }
backup_database() { ... }
# 3. 适当的参数验证
my_function() {
local required_param=$1
# 验证参数
if [ -z "$required_param" ]; then
echo "错误:缺少必要参数" >&2
return 1
fi
# 主逻辑
# ...
}
3.10.2 文档和注释
bash
#!/bin/bash
##
# 计算两个数的和
#
# @param $1 第一个数字
# @param $2 第二个数字
# @return 输出两数之和
# @example
# sum=$(add 10 20)
# echo $sum # 30
##
add() {
local num1=$1
local num2=$2
# 验证输入是否为数字
if ! is_number "$num1" || ! is_number "$num2"; then
echo "错误:参数必须是数字" >&2
return 1
fi
# 计算并返回结果
echo $((num1 + num2))
}
3.10.3 错误处理模式
bash
# 模式1:使用返回值
process() {
if [ 条件 ]; then
return 0 # 成功
else
return 1 # 失败
fi
}
# 模式2:使用echo返回值,$?表示成功/失败
process() {
if [ 条件 ]; then
echo "结果"
return 0
else
echo "错误信息" >&2
return 1
fi
}
# 模式3:使用全局变量存储错误信息
declare -g ERROR_MSG=""
process() {
if [ 条件 ]; then
return 0
else
ERROR_MSG="发生错误"
return 1
fi
}