完整bash语法教程:从零到专家

2025博客之星年度评选已开启 10w+人浏览 451人参与

完整bash语法教程:从零到专家

第一部分:bash基础概念

1. 什么是bash?

#!/bin/bash
# 这是bash脚本的第一行,称为shebang,指定解释器
# bash是Unix/Linux系统的命令行解释器,也是脚本语言

# 执行方式1:直接执行(需要执行权限)
# chmod +x script.sh
# ./script.sh

# 执行方式2:使用bash解释器(不需要执行权限)
# bash script.sh

# 执行方式3:source命令(在当前shell执行)
# source script.sh 或 . script.sh

2. 基本语法结构

#!/bin/bash
# 第一个bash脚本
echo "Hello, bash!"  # echo命令输出文本
echo "当前目录:" $(pwd)  # $(command)执行命令并获取输出
echo "用户:" $USER

# 输出结果:
# Hello, bash!
# 当前目录: /home/user
# 用户: username

第二部分:变量和数据类型

1. 变量定义和使用

#!/bin/bash

# 1. 定义变量(等号前后不能有空格)
name="Alice"
age=25
salary=5000.50

# 2. 使用变量
echo "姓名: $name"          # 使用$前缀
echo "年龄: ${age}"        # 使用{}明确变量边界
echo "薪水: $salary"

# 3. 只读变量
readonly country="China"
# country="USA"  # 取消注释会报错:readonly variable

# 4. 删除变量
unset salary
echo "薪水: $salary"        # 输出空值

# 5. 变量类型
declare -i int_var=10      # 整数变量
declare -r const=100       # 只读变量
declare -l lower="HELLO"   # 自动转为小写
declare -u upper="hello"   # 自动转为大写

echo "整数: $int_var"
echo "小写: $lower"        # 输出: hello
echo "大写: $upper"        # 输出: HELLO

# 输出结果:
# 姓名: Alice
# 年龄: 25
# 薪水: 5000.50
# 薪水:
# 整数: 10
# 小写: hello
# 大写: HELLO

2. 特殊变量

#!/bin/bash

# 运行脚本: bash special_vars.sh arg1 arg2 "arg 3"

echo "脚本名: $0"            # 脚本名称
echo "参数个数: $#"          # 参数数量
echo "所有参数: $*"          # 所有参数作为一个字符串
echo "所有参数列表: $@"      # 所有参数的列表
echo "第一个参数: $1"        # 第一个参数
echo "第二个参数: $2"        # 第二个参数
echo "第三个参数: $3"        # 第三个参数
echo "进程ID: $$"            # 当前脚本进程ID
echo "退出状态: $?"          # 上一条命令的退出状态
echo "后台进程: $!"          # 最后一个后台进程ID

# 测试命令执行状态
ls /tmp > /dev/null
echo "ls命令状态: $?"       # 0表示成功

ls /nonexistent 2>/dev/null
echo "错误命令状态: $?"     # 非0表示失败

# 输出结果:
# 脚本名: special_vars.sh
# 参数个数: 3
# 所有参数: arg1 arg2 arg 3
# 所有参数列表: arg1 arg2 arg 3
# 第一个参数: arg1
# 第二个参数: arg2
# 第三个参数: arg 3
# 进程ID: 12345
# 退出状态: 0
# 后台进程:
# ls命令状态: 0
# 错误命令状态: 2

3. 数组操作

#!/bin/bash

# 1. 定义数组
fruits=("apple" "banana" "orange" "grape")

# 2. 访问数组元素
echo "第一个水果: ${fruits[0]}"     # 索引从0开始
echo "所有水果: ${fruits[@]}"        # 所有元素
echo "水果个数: ${#fruits[@]}"       # 数组长度

# 3. 遍历数组
echo "遍历数组:"
for fruit in "${fruits[@]}"; do
    echo "  - $fruit"
done

# 4. 修改数组
fruits[1]="mango"                   # 修改元素
fruits+=("peach")                   # 添加元素
echo "修改后的数组: ${fruits[@]}"

# 5. 关联数组(bash 4.0+)
declare -A person
person["name"]="Bob"
person["age"]="30"
person["city"]="New York"

echo "关联数组:"
for key in "${!person[@]}"; do      # 获取所有键
    echo "  $key: ${person[$key]}"
done

# 6. 删除数组元素
unset fruits[2]
echo "删除后: ${fruits[@]}"

# 输出结果:
# 第一个水果: apple
# 所有水果: apple banana orange grape
# 水果个数: 4
# 遍历数组:
#   - apple
#   - banana
#   - orange
#   - grape
# 修改后的数组: apple mango orange grape peach
# 关联数组:
#   name: Bob
#   age: 30
#   city: New York
# 删除后: apple mango grape peach

第三部分:字符串操作

1. 字符串基础

#!/bin/bash

str1="Hello"
str2="World"

# 1. 字符串拼接
greeting="$str1, $str2!"
echo "拼接: $greeting"              # Hello, World!

# 2. 字符串长度
echo "长度: ${#greeting}"           # 13

# 3. 字符串提取
echo "提取1-5: ${greeting:0:5}"    # Hello
echo "提取7-end: ${greeting:7}"     # World!

# 4. 字符串查找和替换
text="I love apples, apples are tasty"
echo "原始: $text"
echo "替换第一个: ${text/apples/oranges}"      # I love oranges, apples are tasty
echo "替换全部: ${text//apples/oranges}"       # I love oranges, oranges are tasty
echo "替换前缀: ${text/#I love/He likes}"      # He likes apples, apples are tasty
echo "替换后缀: ${text/%tasty/delicious}"      # I love apples, apples are delicious

# 5. 大小写转换
mixed="Hello World"
echo "大写: ${mixed^^}"             # HELLO WORLD
echo "小写: ${mixed,,}"             # hello world
echo "首字母大写: ${mixed^}"        # Hello World

# 6. 删除子串
path="/usr/local/bin/script.sh"
echo "原始路径: $path"
echo "删除最短前缀: ${path#*/}"     # usr/local/bin/script.sh
echo "删除最长前缀: ${path##*/}"    # script.sh
echo "删除最短后缀: ${path%/*}"     # /usr/local/bin
echo "删除最长后缀: ${path%%/*}"    # (空)

# 7. 默认值
unset maybe_empty
echo "默认值: ${maybe_empty:-默认文本}"   # 默认文本
echo "变量值: ${maybe_empty}"           # (空)

maybe_empty="有值"
echo "默认值: ${maybe_empty:-默认文本}"   # 有值

# 输出结果:
# 拼接: Hello, World!
# 长度: 13
# 提取1-5: Hello
# 提取7-end: World!
# 原始: I love apples, apples are tasty
# 替换第一个: I love oranges, apples are tasty
# 替换全部: I love oranges, oranges are tasty
# 替换前缀: He likes apples, apples are tasty
# 替换后缀: I love apples, apples are delicious
# 大写: HELLO WORLD
# 小写: hello world
# 首字母大写: Hello World
# 原始路径: /usr/local/bin/script.sh
# 删除最短前缀: usr/local/bin/script.sh
# 删除最长前缀: script.sh
# 删除最短后缀: /usr/local/bin
# 删除最长后缀:
# 默认值: 默认文本
# 变量值:
# 默认值: 有值

第四部分:运算符

1. 算术运算符

#!/bin/bash

a=10
b=3

# 1. 使用 $(( ))
echo "加法: $((a + b))"        # 13
echo "减法: $((a - b))"        # 7
echo "乘法: $((a * b))"        # 30
echo "除法: $((a / b))"        # 3 (整数除法)
echo "取余: $((a % b))"        # 1
echo "指数: $((a ** b))"       # 1000

# 2. 使用 let
let "c = a + b"
echo "let结果: $c"             # 13

# 3. 使用 expr (旧方法)
d=$(expr $a + $b)
echo "expr结果: $d"            # 13

# 4. 浮点数运算 (使用bc)
e=$(echo "scale=2; $a / $b" | bc)
echo "浮点除法: $e"            # 3.33

# 5. 自增自减
((a++))
echo "自增后: $a"              # 11
((b--))
echo "自减后: $b"              # 2

# 6. 位运算
x=5  # 0101
y=3  # 0011
echo "与运算: $((x & y))"      # 1 (0001)
echo "或运算: $((x | y))"      # 7 (0111)
echo "异或: $((x ^ y))"        # 6 (0110)
echo "左移: $((x << 1))"       # 10 (1010)
echo "右移: $((x >> 1))"       # 2 (0010)

# 输出结果:
# 加法: 13
# 减法: 7
# 乘法: 30
# 除法: 3
# 取余: 1
# 指数: 1000
# let结果: 13
# expr结果: 13
# 浮点除法: 3.33
# 自增后: 11
# 自减后: 2
# 与运算: 1
# 或运算: 7
# 异或: 6
# 左移: 10
# 右移: 2

2. 关系运算符

#!/bin/bash

x=10
y=20
z=10

# 数值比较
echo "数值比较:"
echo "x > y: $((x > y))"       # 0 (假)
echo "x < y: $((x < y))"       # 1 (真)
echo "x >= z: $((x >= z))"     # 1
echo "x <= z: $((x <= z))"     # 1
echo "x == z: $((x == z))"     # 1
echo "x != y: $((x != y))"     # 1

# 字符串比较
str1="hello"
str2="world"
str3="hello"

echo -e "\n字符串比较:"
if [[ "$str1" == "$str3" ]]; then
    echo "str1等于str3"         # 输出
fi

if [[ "$str1" != "$str2" ]]; then
    echo "str1不等于str2"       # 输出
fi

if [[ -z "" ]]; then
    echo "空字符串"             # 输出
fi

if [[ -n "$str1" ]]; then
    echo "非空字符串"           # 输出
fi

# 输出结果:
# 数值比较:
# x > y: 0
# x < y: 1
# x >= z: 1
# x <= z: 1
# x == z: 1
# x != y: 1
# 
# 字符串比较:
# str1等于str3
# str1不等于str2
# 空字符串
# 非空字符串

3. 逻辑运算符

#!/bin/bash

a=10
b=20

# 逻辑运算
if [[ $a -lt 15 && $b -gt 15 ]]; then
    echo "a<15 且 b>15"        # 输出
fi

if [[ $a -gt 15 || $b -gt 15 ]]; then
    echo "a>15 或 b>15"        # 输出
fi

if [[ ! $a -gt 15 ]]; then
    echo "a不大于15"           # 输出
fi

# 短路运算
[[ -f "/etc/passwd" ]] && echo "文件存在"   # 输出
[[ -f "/nonexistent" ]] || echo "文件不存在" # 输出

# 输出结果:
# a<15 且 b>15
# a>15 或 b>15
# a不大于15
# 文件存在
# 文件不存在

第五部分:控制结构

1. 条件判断

#!/bin/bash

# 1. if-elif-else
num=75

if [[ $num -ge 90 ]]; then
    echo "优秀"
elif [[ $num -ge 80 ]]; then
    echo "良好"
elif [[ $num -ge 70 ]]; then
    echo "中等"                 # 输出
elif [[ $num -ge 60 ]]; then
    echo "及格"
else
    echo "不及格"
fi

# 2. case语句
fruit="apple"

case $fruit in
    "apple")
        echo "这是苹果"         # 输出
        ;;
    "banana")
        echo "这是香蕉"
        ;;
    "orange" | "lemon")
        echo "这是柑橘类"
        ;;
    *)
        echo "未知水果"
        ;;
esac

# 3. 测试命令 test 和 [ ]
file="/etc/passwd"

if test -f "$file"; then
    echo "文件存在(使用test)"   # 输出
fi

if [ -f "$file" ]; then
    echo "文件存在(使用[ ])"    # 输出
fi

if [[ -f "$file" && -r "$file" ]]; then
    echo "文件存在且可读"       # 输出
fi

# 输出结果:
# 中等
# 这是苹果
# 文件存在(使用test)
# 文件存在(使用[ ])
# 文件存在且可读

2. 循环结构

#!/bin/bash

# 1. for循环
echo "for循环示例:"

# 方式1:遍历列表
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

# 方式2:遍历命令输出
for file in $(ls /tmp | head -3); do
    echo "文件: $file"
done

# 方式3:C语言风格
for ((i=1; i<=3; i++)); do
    echo "计数: $i"
done

# 2. while循环
echo -e "\nwhile循环示例:"
count=1
while [[ $count -le 3 ]]; do
    echo "while: $count"
    ((count++))
done

# 3. until循环
echo -e "\nuntil循环示例:"
num=1
until [[ $num -gt 3 ]]; do
    echo "until: $num"
    ((num++))
done

# 4. 无限循环
echo -e "\n无限循环(按Ctrl+C停止):"
# while true; do
#     echo "循环中..."
#     sleep 1
# done

# 5. 循环控制
echo -e "\n循环控制:"
for i in {1..10}; do
    if [[ $i -eq 3 ]]; then
        continue  # 跳过本次循环
    fi
    
    if [[ $i -eq 8 ]]; then
        break     # 跳出循环
    fi
    
    echo "处理: $i"
done

# 输出结果:
# for循环示例:
# 数字: 1
# 数字: 2
# 数字: 3
# 数字: 4
# 数字: 5
# 文件: file1
# 文件: file2
# 文件: file3
# 计数: 1
# 计数: 2
# 计数: 3
# 
# while循环示例:
# while: 1
# while: 2
# while: 3
# 
# until循环示例:
# until: 1
# until: 2
# until: 3
# 
# 循环控制:
# 处理: 1
# 处理: 2
# 处理: 4
# 处理: 5
# 处理: 6
# 处理: 7

第六部分:函数

1. 函数基础

#!/bin/bash

# 1. 函数定义
say_hello() {
    local name=$1  # local定义局部变量
    echo "Hello, $name!"
    return 0       # 返回状态码(0-255)
}

# 2. 调用函数
say_hello "Alice"  # 传递参数
echo "返回值: $?"  # 获取返回值

# 3. 带返回值的函数
add() {
    local sum=$(( $1 + $2 ))
    echo $sum      # 通过echo返回值(可以返回字符串)
}

result=$(add 10 20)
echo "加法结果: $result"

# 4. 参数处理
process_args() {
    echo "参数个数: $#"
    echo "所有参数: $@"
    echo "第一个参数: $1"
    echo "第二个参数: $2"
    
    # 遍历所有参数
    for arg in "$@"; do
        echo "参数: $arg"
    done
}

process_args "arg1" "arg2" "arg3"

# 5. 递归函数
factorial() {
    local n=$1
    if [[ $n -le 1 ]]; then
        echo 1
    else
        local prev=$(factorial $((n-1)))
        echo $((n * prev))
    fi
}

echo "5的阶乘: $(factorial 5)"

# 输出结果:
# Hello, Alice!
# 返回值: 0
# 加法结果: 30
# 参数个数: 3
# 所有参数: arg1 arg2 arg3
# 第一个参数: arg1
# 第二个参数: arg2
# 参数: arg1
# 参数: arg2
# 参数: arg3
# 5的阶乘: 120

第七部分:输入输出

1. 标准输入输出

#!/bin/bash

# 1. 读取用户输入
echo -n "请输入您的名字: "
read username
echo "你好, $username!"

# 2. 读取多个值
echo -n "请输入姓名和年龄: "
read name age
echo "姓名: $name, 年龄: $age"

# 3. 静默读取(用于密码)
echo -n "请输入密码: "
read -s password
echo -e "\n密码已接收"

# 4. 超时设置
echo -n "请在5秒内输入: "
read -t 5 timeout_input || echo "时间到!"
echo "输入: $timeout_input"

# 5. 读取文件行
echo "读取/etc/passwd前3行:"
count=0
while IFS= read -r line && [[ $count -lt 3 ]]; do
    echo "行 $((count+1)): $line"
    ((count++))
done < /etc/passwd

# 6. 重定向
echo "重定向示例:"
echo "这是标准输出" > output.txt          # 覆盖写入
echo "这是追加内容" >> output.txt         # 追加写入
cat output.txt

# 错误重定向
ls /nonexistent 2> error.log             # 错误输出到文件
ls /tmp &> all_output.log                # 所有输出到文件

# 7. 管道
echo -e "\n管道示例:"
echo -e "apple\nbanana\ncherry\napple" | sort | uniq -c

# 8. Here Document
cat << EOF
这是Here Document
可以输入多行文本
直到遇到结束标记EOF
EOF

# 输出结果:
# 请输入您的名字: Alice
# 你好, Alice!
# 请输入姓名和年龄: Bob 25
# 姓名: Bob, 年龄: 25
# 请输入密码: 
# 密码已接收
# 请在5秒内输入: hello
# 输入: hello
# 读取/etc/passwd前3行:
# 行 1: root:x:0:0:root:/root:/bin/bash
# 行 2: daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# 行 3: bin:x:2:2:bin:/bin:/usr/sbin/nologin
# 重定向示例:
# 这是标准输出
# 这是追加内容
# 
# 管道示例:
#       2 apple
#       1 banana
#       1 cherry
# 这是Here Document
# 可以输入多行文本
# 直到遇到结束标记EOF

第八部分:高级特性

1. 进程和作业控制

#!/bin/bash

# 1. 后台运行
echo "启动后台进程:"
sleep 5 &
bg_pid=$!
echo "后台进程PID: $bg_pid"

# 2. 等待进程
echo "等待后台进程完成..."
wait $bg_pid
echo "后台进程完成"

# 3. 作业控制
echo -e "\n作业控制:"
sleep 3 &
sleep 2 &

jobs          # 显示作业列表
fg %1         # 将作业1调到前台
jobs          # 再次显示

# 4. 信号处理
trap "echo '收到SIGINT信号'; exit 1" SIGINT

echo "按Ctrl+C测试信号处理"
# sleep 10  # 取消注释测试

# 5. 子shell
echo -e "\n子shell示例:"
(
    echo "子shell中"
    cd /tmp
    pwd  # 输出: /tmp
)
pwd  # 输出原始目录

# 输出结果:
# 启动后台进程:
# 后台进程PID: 12345
# 等待后台进程完成...
# 后台进程完成
# 
# 作业控制:
# [1]-  运行中               sleep 3 &
# [2]+  运行中               sleep 2 &
# 
# 按Ctrl+C测试信号处理
# 
# 子shell示例:
# 子shell中
# /tmp
# /home/user

2. 调试和错误处理

#!/bin/bash

# 1. 调试模式
set -x  # 开启调试,显示执行的命令
echo "调试模式开始"
debug_var="test"
echo "变量值: $debug_var"
set +x  # 关闭调试
echo "调试模式结束"

# 2. 错误处理选项
set -e  # 遇到错误立即退出
set -u  # 使用未定义变量时报错
set -o pipefail  # 管道中任一命令失败则整个管道失败

# 3. 自定义错误处理
error_handler() {
    echo "错误发生在第 $1 行"
    echo "命令: $2"
    echo "退出码: $3"
}

trap 'error_handler ${LINENO} "$BASH_COMMAND" $?' ERR

# 测试错误
ls /nonexistent  # 这会产生错误

# 4. 脚本选项处理
while getopts ":a:b:c" opt; do
    case $opt in
        a)
            echo "选项-a的值: $OPTARG"
            ;;
        b)
            echo "选项-b的值: $OPTARG"
            ;;
        c)
            echo "选项-c被设置"
            ;;
        \?)
            echo "无效选项: -$OPTARG"
            ;;
        :)
            echo "选项-$OPTARG需要参数"
            ;;
    esac
done

# 运行: bash script.sh -a value1 -b value2 -c

# 输出结果:
# + echo '调试模式开始'
# 调试模式开始
# + debug_var=test
# + echo '变量值: test'
# 变量值: test
# + set +x
# 调试模式结束
# 错误发生在第 27 行
# 命令: ls /nonexistent
# 退出码: 2
# 选项-a的值: value1
# 选项-b的值: value2
# 选项-c被设置

第九部分:实战示例

1. 系统监控脚本

#!/bin/bash
# 系统监控脚本

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# 获取系统信息
get_system_info() {
    echo -e "${GREEN}====== 系统信息 ======${NC}"
    
    # 主机信息
    echo -e "${YELLOW}主机名:${NC} $(hostname)"
    echo -e "${YELLOW}系统版本:${NC} $(cat /etc/os-release | grep PRETTY_NAME | cut -d'"' -f2)"
    echo -e "${YELLOW}内核版本:${NC} $(uname -r)"
    echo -e "${YELLOW}运行时间:${NC} $(uptime -p | sed 's/up //')"
    
    # CPU信息
    local load=$(uptime | awk -F'load average:' '{print $2}')
    echo -e "${YELLOW}负载:${NC} $load"
    
    # 内存信息
    local mem_total=$(free -h | awk '/Mem:/ {print $2}')
    local mem_used=$(free -h | awk '/Mem:/ {print $3}')
    local mem_percent=$(free | awk '/Mem:/ {printf "%.1f", $3/$2*100}')
    
    echo -e "${YELLOW}内存使用:${NC} $mem_used/$mem_total ($mem_percent%)"
    
    # 磁盘信息
    local disk_usage=$(df -h / | awk 'NR==2 {print $5}')
    echo -e "${YELLOW}根分区使用:${NC} $disk_usage"
    
    # 进程信息
    local process_count=$(ps aux | wc -l)
    echo -e "${YELLOW}进程数:${NC} $((process_count-1))"
    
    # 用户信息
    local user_count=$(who | wc -l)
    echo -e "${YELLOW}登录用户:${NC} $user_count"
}

# 检查服务状态
check_service() {
    local service=$1
    if systemctl is-active --quiet $service; then
        echo -e "  $service: ${GREEN}运行中${NC}"
    else
        echo -e "  $service: ${RED}停止${NC}"
    fi
}

# 检查端口
check_port() {
    local port=$1
    if netstat -tuln | grep ":$port " > /dev/null; then
        echo -e "  端口$port: ${GREEN}监听中${NC}"
    else
        echo -e "  端口$port: ${RED}未监听${NC}"
    fi
}

# 主函数
main() {
    get_system_info
    
    echo -e "\n${GREEN}====== 服务状态 ======${NC}"
    check_service "sshd"
    check_service "nginx"
    check_service "mysql"
    
    echo -e "\n${GREEN}====== 端口状态 ======${NC}"
    check_port 22
    check_port 80
    check_port 3306
    
    # 告警检查
    echo -e "\n${GREEN}====== 告警检查 ======${NC}"
    
    # 内存检查
    local mem_percent=$(free | awk '/Mem:/ {printf "%.0f", $3/$2*100}')
    if [[ $mem_percent -gt 90 ]]; then
        echo -e "${RED}警告: 内存使用率超过90%!${NC}"
    elif [[ $mem_percent -gt 80 ]]; then
        echo -e "${YELLOW}注意: 内存使用率超过80%${NC}"
    else
        echo -e "${GREEN}内存使用正常${NC}"
    fi
    
    # 磁盘检查
    local disk_percent=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
    if [[ $disk_percent -gt 90 ]]; then
        echo -e "${RED}警告: 磁盘使用率超过90%!${NC}"
    elif [[ $disk_percent -gt 80 ]]; then
        echo -e "${YELLOW}注意: 磁盘使用率超过80%${NC}"
    else
        echo -e "${GREEN}磁盘使用正常${NC}"
    fi
}

# 运行主函数
main

2. 备份脚本

#!/bin/bash
# 文件备份脚本

CONFIG_FILE="backup.conf"
LOG_FILE="backup.log"

# 读取配置文件
if [[ -f "$CONFIG_FILE" ]]; then
    source "$CONFIG_FILE"
else
    # 默认配置
    BACKUP_DIR="./backups"
    SOURCE_DIRS=("." "/etc")
    RETENTION_DAYS=7
    COMPRESS=true
fi

# 创建备份目录
mkdir -p "$BACKUP_DIR"

# 日志函数
log_message() {
    local level=$1
    local message=$2
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

# 错误处理
handle_error() {
    log_message "ERROR" "备份失败: $1"
    exit 1
}

# 执行备份
perform_backup() {
    local backup_name="backup_$(date '+%Y%m%d_%H%M%S')"
    local backup_path="$BACKUP_DIR/$backup_name"
    
    log_message "INFO" "开始备份: $backup_name"
    
    # 创建临时目录
    mkdir -p "$backup_path" || handle_error "无法创建备份目录"
    
    # 备份每个目录
    for dir in "${SOURCE_DIRS[@]}"; do
        if [[ -d "$dir" ]]; then
            log_message "INFO" "备份目录: $dir"
            
            local dir_name=$(basename "$dir")
            if [[ "$dir_name" == "." ]]; then
                dir_name="current"
            fi
            
            cp -r "$dir" "$backup_path/$dir_name" || \
                log_message "WARNING" "无法备份 $dir"
        else
            log_message "WARNING" "目录不存在: $dir"
        fi
    done
    
    # 压缩备份
    if [[ "$COMPRESS" == "true" ]]; then
        log_message "INFO" "压缩备份文件..."
        tar -czf "$backup_path.tar.gz" -C "$BACKUP_DIR" "$backup_name" || \
            handle_error "压缩失败"
        
        # 删除未压缩的目录
        rm -rf "$backup_path"
        backup_path="$backup_path.tar.gz"
    fi
    
    log_message "INFO" "备份完成: $(du -h "$backup_path" | cut -f1)"
    echo "$backup_path"
}

# 清理旧备份
cleanup_old_backups() {
    log_message "INFO" "清理 $RETENTION_DAYS 天前的备份..."
    
    local deleted_count=0
    for backup in "$BACKUP_DIR"/backup_*; do
        if [[ -f "$backup" ]] || [[ -d "$backup" ]]; then
            local backup_age=$(( ( $(date +%s) - $(stat -c %Y "$backup") ) / 86400 ))
            
            if [[ $backup_age -gt $RETENTION_DAYS ]]; then
                log_message "INFO" "删除旧备份: $(basename "$backup") (${backup_age}天)"
                rm -rf "$backup"
                ((deleted_count++))
            fi
        fi
    done
    
    log_message "INFO" "清理完成,删除了 $deleted_count 个旧备份"
}

# 主函数
main() {
    log_message "INFO" "=== 备份脚本开始 ==="
    
    # 检查源目录
    local valid_dirs=0
    for dir in "${SOURCE_DIRS[@]}"; do
        if [[ -d "$dir" ]]; then
            ((valid_dirs++))
        fi
    done
    
    if [[ $valid_dirs -eq 0 ]]; then
        handle_error "没有有效的源目录"
    fi
    
    # 执行备份
    backup_file=$(perform_backup)
    
    # 清理旧备份
    cleanup_old_backups
    
    # 生成报告
    local total_size=$(du -sh "$BACKUP_DIR" | cut -f1)
    local backup_count=$(ls -1 "$BACKUP_DIR"/backup_* 2>/dev/null | wc -l)
    
    log_message "INFO" "备份统计:"
    log_message "INFO" "  备份目录: $BACKUP_DIR"
    log_message "INFO" "  总大小: $total_size"
    log_message "INFO" "  备份数量: $backup_count"
    log_message "INFO" "  最新备份: $(basename "$backup_file")"
    
    log_message "INFO" "=== 备份脚本结束 ==="
    
    # 发送通知(可选)
    # send_notification "备份完成: $(basename "$backup_file")"
}

# 运行主函数
main "$@"

第十部分:最佳实践总结

1. 安全实践

#!/bin/bash
# bash最佳实践示例

# 1. 使用set命令增强安全性
set -euo pipefail  # 启用严格模式
set -o nounset     # 使用未定义变量时报错
set -o errexit     # 命令失败时退出
set -o pipefail    # 管道失败时退出

# 2. 总是引用变量
path="/path with spaces"
# 错误: ls $path
# 正确:
ls "$path"

# 3. 使用[[ ]]代替[ ]进行测试
if [[ -f "$file" && -r "$file" ]]; then
    # 更安全,功能更强大
fi

# 4. 使用函数提高可重用性
log() {
    local level=$1
    local message=$2
    echo "[$(date)] [$level] $message"
}

# 5. 使用local定义局部变量
calculate() {
    local result=$(( $1 + $2 ))
    echo "$result"
}

# 6. 验证输入
validate_input() {
    if [[ -z "$1" ]]; then
        echo "错误: 参数不能为空"
        exit 1
    fi
    
    if [[ ! "$1" =~ ^[a-zA-Z]+$ ]]; then
        echo "错误: 只允许字母"
        exit 1
    fi
}

# 7. 使用trap清理资源
cleanup() {
    rm -f "$TEMP_FILE"
    echo "清理完成"
}

trap cleanup EXIT

# 8. 提供使用帮助
usage() {
    cat << EOF
用法: $0 [选项] <参数>
选项:
  -h, --help     显示帮助信息
  -v, --version  显示版本信息
  -d, --debug    启用调试模式
示例:
  $0 --debug test
EOF
}

# 9. 使用getopts处理参数
while getopts ":hvd:" opt; do
    case $opt in
        h) usage; exit 0 ;;
        v) echo "版本 1.0.0"; exit 0 ;;
        d) DEBUG="$OPTARG" ;;
        \?) echo "无效选项: -$OPTARG" >&2; exit 1 ;;
        :) echo "选项 -$OPTARG 需要参数" >&2; exit 1 ;;
    esac
done

# 10. 添加脚本头信息
: '
脚本名称: best_practice.sh
作者: Your Name
版本: 1.0.0
描述: bash最佳实践示例
创建日期: 2024-01-01
修改历史:
  2024-01-01: 创建脚本
'

# 11. 使用颜色和格式输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo -e "${GREEN}成功消息${NC}"
echo -e "${RED}错误消息${NC}"
echo -e "${YELLOW}警告消息${NC}"

2. 性能优化

#!/bin/bash
# 性能优化示例

# 1. 避免不必要的子shell
# 不好:
result=$(echo "$var" | tr 'a-z' 'A-Z')
# 好:
result="${var^^}"

# 2. 使用内置字符串操作代替外部命令
# 不好:
length=$(echo "$str" | wc -c)
# 好:
length=${#str}

# 3. 批量处理而不是循环处理
# 不好:
for file in *.txt; do
    mv "$file" "${file%.txt}.bak"
done
# 好:
rename '.txt' '.bak' *.txt

# 4. 使用数组代替多个变量
# 不好:
item1="value1"
item2="value2"
item3="value3"
# 好:
items=("value1" "value2" "value3")

# 5. 使用here string代替echo管道
# 不好:
echo "$data" | grep "pattern"
# 好:
grep "pattern" <<< "$data"

# 6. 缓存命令输出
# 不好: 多次执行相同命令
if [[ $(who | wc -l) -gt 5 ]]; then
    echo "用户数: $(who | wc -l)"
fi
# 好: 缓存结果
user_count=$(who | wc -l)
if [[ $user_count -gt 5 ]]; then
    echo "用户数: $user_count"
fi

# 7. 使用算术展开代替expr
# 不好:
result=$(expr $a + $b)
# 好:
result=$((a + b))

# 8. 提前退出减少嵌套
# 不好:
if [[ -f "$file" ]]; then
    if [[ -r "$file" ]]; then
        # 处理文件
    fi
fi
# 好:
[[ -f "$file" ]] || exit 1
[[ -r "$file" ]] || exit 1
# 处理文件

# 9. 使用函数减少重复代码
process_file() {
    local file=$1
    [[ -f "$file" ]] || return 1
    # 处理逻辑
}

# 10. 并行处理
process_items() {
    local item=$1
    # 处理单个项目
}

# 顺序处理
# for item in "${items[@]}"; do
#     process_items "$item"
# done

# 并行处理(如果有大量数据)
for item in "${items[@]}"; do
    process_items "$item" &
done
wait

通过这个完整教程,你已经掌握了bash从基础到高级的所有概念。记住:

  1. 实践是最好的老师 - 多写脚本解决实际问题
  2. 阅读优秀代码 - 学习开源项目的bash脚本
  3. 掌握调试技巧 - 使用set -xecho调试
  4. 关注安全性 - 避免常见的安全漏洞
  5. 保持代码简洁 - 复杂的逻辑考虑用其他语言实现

祝你成为bash专家!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值