Shell 高级用法

一、基本运算符

Shell支持多种运算符,主要包括:

  1. 算数运算符
  2. 关系运算符
  3. 布尔运算符
  4. 字符串运算符
  5. 文件测试运算符
  6. 位运算符
  7. 赋值运算符

1.1 算术运算符

1.1.1 基本算术运算

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用,主要用于数字间的算术运算。常用的算术运算符有:+(加)-(减)*(乘)/(除)%(取余)=(赋值)==(等于)!=(不等于)。需要注意以下几点:

  1. 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2
  2. 完整的表达式要被 `` 包含,注意这个字符不是常用的单引号
  3. 乘号 * 前边必须加反斜杠 \ 才能实现乘法运算。
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
}
相关推荐
Hello_wshuo1 天前
记RP2040使用Arduino+platformio开发配置
linux·嵌入式硬件·arduino
KingRumn1 天前
DBUS源码剖析之DBusMessage消息头
linux·服务器·算法
pwn蒸鱼1 天前
buuctf中的pwn2_sctf_2016(libc泄露+栈溢出)
linux·安全
qq_5470261791 天前
Linux 压缩与解压缩
linux·运维·服务器
QT 小鲜肉1 天前
【Linux命令大全】003.文档编辑之csplit命令(实操篇)
linux·运维·服务器·chrome·mysql
itas1091 天前
Linux交叉编译工具链
linux·运维·服务器·交叉编译·cross-compile
胖好白1 天前
【咸鱼RK3399】打造NAS(Debian+Docker+CasaOS)
linux·docker·debian
zfxwasaboy1 天前
DRM KMS 子系统(2)Framebuffer
linux·c语言
QT 小鲜肉1 天前
【Linux命令大全】002.文件传输之lpr命令(实操篇)
linux·运维·服务器·网络·chrome·笔记