完整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从基础到高级的所有概念。记住:
- 实践是最好的老师 - 多写脚本解决实际问题
- 阅读优秀代码 - 学习开源项目的bash脚本
- 掌握调试技巧 - 使用
set -x、echo调试 - 关注安全性 - 避免常见的安全漏洞
- 保持代码简洁 - 复杂的逻辑考虑用其他语言实现
祝你成为bash专家!
800

被折叠的 条评论
为什么被折叠?



