1. Debian系统控制结构基础解析
在Debian系统的日常运维工作中,shell脚本控制结构是自动化管理的核心工具。这些结构允许我们根据条件执行不同操作、重复执行任务直到满足特定条件,或者基于不同情况选择不同处理路径。掌握这些控制结构,意味着你能将重复性工作转化为高效的自动化流程。
控制结构主要分为三类:条件判断(if/case)、循环(for/while/until)和流程控制(break/continue)。每种结构都有其适用场景:
- 条件判断:适合处理需要根据不同状态执行不同操作的场景,比如检查服务状态、验证文件存在性等
- 循环结构:适合批量处理文件、监控状态变化或执行重复性任务
- 流程控制:用于优化循环效率或在特定条件下中断/跳过某些操作
在Debian系统中,这些控制结构通常使用Bash shell语法实现。Bash作为大多数Linux发行版的默认shell,其控制结构语法具有很好的可移植性,编写的脚本可以在不同Linux系统间迁移使用。
提示:虽然本文以Debian为例,但所述控制结构同样适用于其他Linux发行版,只需注意个别命令的差异
2. 条件判断结构详解与应用
2.1 if条件语句实战
if语句是shell脚本中最基础的条件判断结构,其标准语法为:
bash复制if [ 条件测试 ]; then
# 条件为真时执行的命令
fi
条件测试可以使用test命令或双方括号[[ ]]实现。在Debian系统维护中,常见的条件测试包括:
- 文件测试:检查文件存在性、类型、权限等
- 字符串比较:验证变量内容或用户输入
- 数值比较:检查系统资源使用情况
- 命令返回值:判断上一条命令是否执行成功
文件检查实例
bash复制#!/bin/bash
# 检查关键配置文件是否存在
CONFIG_FILE="/etc/nginx/nginx.conf"
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误:配置文件 $CONFIG_FILE 不存在"
exit 1
fi
if [ ! -r "$CONFIG_FILE" ]; then
echo "错误:当前用户没有读取 $CONFIG_FILE 的权限"
exit 1
fi
这个例子演示了如何检查文件存在性和可读性。-f测试文件是否存在且为普通文件,-r测试当前用户是否有读取权限。在关键服务启动脚本中,这类检查可以避免因配置问题导致的服务启动失败。
系统资源监控实例
bash复制#!/bin/bash
# 内存使用检查脚本
MEM_THRESHOLD=90
MEM_USAGE=$(free | awk '/Mem/{printf("%.0f"), $3/$2*100}')
if [ "$MEM_USAGE" -gt "$MEM_THRESHOLD" ]; then
echo "警告:内存使用率已达 ${MEM_USAGE}%,超过阈值 ${MEM_THRESHOLD}%"
# 触发报警或自动清理操作
fi
此脚本监控系统内存使用情况,当使用率超过阈值时发出警告。数值比较使用-gt(大于)操作符,类似的还有-lt(小于)、-eq(等于)等。
2.2 if-else与if-elif-else结构
对于更复杂的条件判断,可以使用if-else或if-elif-else结构:
bash复制if [ 条件1 ]; then
# 条件1为真时执行
elif [ 条件2 ]; then
# 条件2为真时执行
else
# 所有条件都不满足时执行
fi
服务状态检查实例
bash复制#!/bin/bash
# 检查服务状态并执行相应操作
SERVICE="apache2"
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE 服务正在运行"
elif systemctl is-enabled --quiet "$SERVICE"; then
echo "$SERVICE 服务已启用但未运行,正在启动..."
systemctl start "$SERVICE"
else
echo "$SERVICE 服务未启用,请先启用服务"
fi
这个例子展示了多条件判断的实际应用:首先检查服务是否正在运行,如果不是则检查是否已启用但未运行,最后处理完全未启用的情况。这种结构在服务管理脚本中非常实用。
2.3 测试条件与逻辑运算符
条件测试是if语句的核心,Debian系统脚本中常用的测试条件包括:
文件测试运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
| -e | 文件/目录是否存在 | [ -e "/tmp/file" ] |
| -f | 是否为普通文件 | [ -f "/etc/passwd" ] |
| -d | 是否为目录 | [ -d "/var/log" ] |
| -s | 文件大小是否大于0 | [ -s "error.log" ] |
| -r | 当前用户是否有读权限 | [ -r "/etc/shadow" ] |
| -w | 当前用户是否有写权限 | [ -w "/var/tmp" ] |
| -x | 当前用户是否有执行权限 | [ -x "/usr/bin/bash" ] |
字符串测试运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
| -z | 字符串是否为空 | [ -z "$VAR" ] |
| -n | 字符串是否非空 | [ -n "$USER" ] |
| = | 字符串是否相等 | [ "$OSTYPE" = "linux-gnu" ] |
| != | 字符串是否不等 | [ "$SHELL" != "/bin/bash" ] |
数值比较运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
| -eq | 等于 (equal) | [ "$UID" -eq 0 ] |
| -ne | 不等于 (not equal) | [ "$PID" -ne 1 ] |
| -gt | 大于 (greater than) | [ "$COUNT" -gt 10 ] |
| -ge | 大于等于 | [ "$VERSION" -ge 18 ] |
| -lt | 小于 (less than) | [ "$AGE" -lt 21 ] |
| -le | 小于等于 | [ "$LEVEL" -le 5 ] |
逻辑运算符组合
多个条件可以通过逻辑运算符组合:
bash复制# 逻辑与:所有条件都为真时返回真
if [ 条件1 ] && [ 条件2 ]; then
# 命令
fi
# 逻辑或:任一条件为真时返回真
if [ 条件1 ] || [ 条件2 ]; then
# 命令
fi
# 逻辑非:反转条件结果
if ! [ 条件 ]; then
# 命令
fi
综合应用实例
bash复制#!/bin/bash
# 综合条件检查脚本
BACKUP_DIR="/backups"
MIN_SPACE=10 # 最小需要10GB空间
LOG_FILE="/var/log/backup.log"
# 检查备份目录是否存在且可写
if [ ! -d "$BACKUP_DIR" ] || [ ! -w "$BACKUP_DIR" ]; then
echo "错误:备份目录 $BACKUP_DIR 不存在或不可写" | tee -a "$LOG_FILE"
exit 1
fi
# 检查磁盘空间是否足够
AVAIL_SPACE=$(df -BG "$BACKUP_DIR" | awk 'NR==2{print $4}' | tr -d 'G')
if [ "$AVAIL_SPACE" -lt "$MIN_SPACE" ]; then
echo "错误:可用空间不足 (${AVAIL_SPACE}G < ${MIN_SPACE}G)" | tee -a "$LOG_FILE"
exit 1
fi
# 检查日志文件是否可写
if [ -f "$LOG_FILE" ] && [ ! -w "$LOG_FILE" ]; then
echo "错误:日志文件 $LOG_FILE 不可写"
exit 1
fi
echo "所有检查通过,开始备份操作..." | tee -a "$LOG_FILE"
这个例子展示了多个条件的组合使用,包括目录存在性检查、可写性验证和磁盘空间检查。tee命令同时将输出显示在屏幕和记录到日志文件。
注意事项:在条件测试中使用变量时,建议将变量用双引号括起来,避免变量为空或包含空格时出现语法错误
3. 循环结构深度解析
3.1 for循环实战应用
for循环是处理已知项目集合的理想选择,在Debian系统管理中常用于批量处理文件、目录或命令行参数。其基本语法有两种形式:
bash复制# 形式1:遍历值列表
for 变量 in 项目1 项目2 项目3...
do
# 循环体
done
# 形式2:类C风格
for (( 初始值; 条件; 步进 ))
do
# 循环体
done
文件批量处理实例
bash复制#!/bin/bash
# 批量重命名.log文件为.log.bak
LOG_DIR="/var/log/myapp"
# 检查目录是否存在
if [ ! -d "$LOG_DIR" ]; then
echo "错误:日志目录 $LOG_DIR 不存在"
exit 1
fi
# 遍历.log文件
count=0
for file in "$LOG_DIR"/*.log
do
if [ -f "$file" ]; then
mv "$file" "${file}.bak"
echo "已重命名: $file → ${file}.bak"
count=$((count + 1))
fi
done
echo "共处理了 $count 个日志文件"
这个脚本展示了如何安全地批量重命名文件。关键点包括:
- 先检查目录是否存在
- 使用通配符*.log匹配目标文件
- 每次循环都验证当前项是否为文件(-f测试)
- 使用计数器记录处理数量
系统用户批量操作实例
bash复制#!/bin/bash
# 为多个用户创建家目录备份
USERS=("webadmin" "dbadmin" "appuser")
BACKUP_ROOT="/backups/home"
for user in "${USERS[@]}"
do
home_dir="/home/$user"
backup_dir="$BACKUP_ROOT/$user"
# 检查用户家目录是否存在
if [ ! -d "$home_dir" ]; then
echo "警告:用户 $user 的家目录不存在"
continue # 跳过当前用户
fi
# 创建备份目录
mkdir -p "$backup_dir"
# 执行备份
timestamp=$(date +%Y%m%d-%H%M%S)
tar -czf "$backup_dir/home_backup_$timestamp.tar.gz" "$home_dir"
echo "已备份 $user 的家目录到 $backup_dir"
done
此例演示了如何遍历用户列表并执行备份操作。使用数组存储用户名,continue语句跳过不存在的家目录,timestamp变量确保每次备份生成唯一的文件名。
3.2 while循环高级用法
while循环在条件为真时持续执行,特别适合处理未知数量的迭代或实现守护进程。基本语法:
bash复制while [ 条件 ]
do
# 循环体
done
服务监控守护实例
bash复制#!/bin/bash
# 监控服务并在异常时自动重启
SERVICE="nginx"
CHECK_INTERVAL=60 # 检查间隔(秒)
MAX_RESTARTS=3 # 最大重启次数
restart_count=0
while true
do
if ! systemctl is-active --quiet "$SERVICE"; then
echo "$(date): 服务 $SERVICE 已停止" >> /var/log/service_monitor.log
if [ "$restart_count" -lt "$MAX_RESTARTS" ]; then
echo "尝试重启 $SERVICE..."
systemctl restart "$SERVICE"
restart_count=$((restart_count + 1))
echo "重启次数: $restart_count/$MAX_RESTARTS" >> /var/log/service_monitor.log
else
echo "已达到最大重启次数 $MAX_RESTARTS,停止尝试" >> /var/log/service_monitor.log
echo "服务 $SERVICE 多次重启失败,请管理员检查" | mail -s "服务故障警报" admin@example.com
exit 1
fi
fi
sleep "$CHECK_INTERVAL"
done
这个守护脚本实现了:
- 持续监控服务状态
- 服务停止时自动重启
- 限制最大重启次数避免无限循环
- 达到限制后发送邮件通知
- 记录详细日志供后续分析
逐行处理文件实例
bash复制#!/bin/bash
# 处理/etc/passwd中的用户信息
OUTPUT_FILE="/tmp/user_report.txt"
# 清空或创建输出文件
: > "$OUTPUT_FILE"
# 读取passwd文件
while IFS=: read -r username _ uid gid desc home shell
do
# 只处理普通用户(UID >= 1000)
if [ "$uid" -ge 1000 ]; then
echo "用户名: $username" >> "$OUTPUT_FILE"
echo "UID/GID: $uid/$gid" >> "$OUTPUT_FILE"
echo "家目录: $home" >> "$OUTPUT_FILE"
echo "Shell: $shell" >> "$OUTPUT_FILE"
# 检查家目录是否存在
if [ -d "$home" ]; then
size=$(du -sh "$home" | awk '{print $1}')
echo "家目录大小: $size" >> "$OUTPUT_FILE"
else
echo "家目录不存在" >> "$OUTPUT_FILE"
fi
echo "-----" >> "$OUTPUT_FILE"
fi
done < /etc/passwd
echo "用户报告已生成: $OUTPUT_FILE"
此脚本展示了while循环逐行读取文件的高级用法:
- IFS=: 设置字段分隔符为冒号(匹配passwd文件格式)
- read命令将每行分割到多个变量
- 只处理普通用户(UID≥1000)
- 收集并输出每个用户的关键信息
- 检查家目录存在性并计算大小
3.3 until循环特殊场景应用
until循环与while循环逻辑相反,它在条件为假时持续执行。语法:
bash复制until [ 条件 ]
do
# 循环体
done
等待资源可用实例
bash复制#!/bin/bash
# 等待数据库服务可用
DB_HOST="localhost"
DB_PORT="3306"
MAX_WAIT=300 # 最大等待秒数
WAIT_INTERVAL=5
elapsed=0
until nc -z "$DB_HOST" "$DB_PORT" || [ "$elapsed" -ge "$MAX_WAIT" ]
do
echo "等待数据库服务($DB_HOST:$DB_PORT)启动...已等待 ${elapsed}秒"
sleep "$WAIT_INTERVAL"
elapsed=$((elapsed + WAIT_INTERVAL))
done
if nc -z "$DB_HOST" "$DB_PORT"; then
echo "数据库服务已可用"
# 执行后续操作
else
echo "错误:等待数据库服务超时"
exit 1
fi
这个脚本展示了until循环的典型应用场景 - 等待某个条件变为真。关键点包括:
- 使用nc命令检查端口是否开放
- 设置最大等待时间避免无限等待
- 显示等待进度信息
- 最终验证服务状态
锁定文件等待实例
bash复制#!/bin/bash
# 等待锁定文件释放
LOCK_FILE="/tmp/package.lock"
TIMEOUT=120
start_time=$(date +%s)
echo "等待锁定文件 $LOCK_FILE 释放..."
until [ ! -f "$LOCK_FILE" ] || [ $(($(date +%s) - start_time)) -gt "$TIMEOUT" ]
do
sleep 1
done
if [ -f "$LOCK_FILE" ]; then
echo "错误:等待锁定文件超时"
exit 1
else
echo "锁定文件已释放,继续操作"
# 创建新的锁定文件
touch "$LOCK_FILE"
# 执行需要独占锁的操作
# ...
# 操作完成后删除锁定文件
rm -f "$LOCK_FILE"
fi
此例实现了基于文件的简单互斥锁机制,确保同一时间只有一个实例执行关键操作。until循环等待锁定文件消失或超时。
3.4 循环控制与优化技巧
在循环中使用break和continue可以更精确地控制流程:
- break:立即退出当前循环
- continue:跳过本次循环剩余部分,进入下一次迭代
搜索文件优化实例
bash复制#!/bin/bash
# 在多个目录中搜索配置文件
SEARCH_TERM="database.password"
SEARCH_DIRS=("/etc" "/opt/app/config" "/home/deploy/.config")
found=0
for dir in "${SEARCH_DIRS[@]}"
do
echo "正在搜索目录: $dir"
if [ ! -d "$dir" ]; then
echo "警告:目录 $dir 不存在"
continue
fi
# 使用find搜索文件
while IFS= read -r file
do
if grep -q "$SEARCH_TERM" "$file"; then
echo "发现匹配文件: $file"
grep "$SEARCH_TERM" "$file"
found=1
break 2 # 跳出2层循环(while和for)
fi
done < <(find "$dir" -type f -name "*.conf" -o -name "*.ini" 2>/dev/null)
if [ "$found" -eq 1 ]; then
break
fi
done
if [ "$found" -eq 0 ]; then
echo "未找到包含 $SEARCH_TERM 的配置文件"
fi
这个脚本展示了循环控制的高级用法:
- 遍历多个目录搜索配置文件
- 使用continue跳过不存在的目录
- 使用break 2直接跳出两层循环
- 使用进程替换< <(command)处理find输出
- 静默处理find的错误输出(2>/dev/null)
性能优化技巧
-
减少循环内外部命令调用:将不变的内容移到循环外
bash复制# 不推荐 - 每次循环都执行date for i in {1..100}; do echo "$(date): 处理项目 $i" done # 推荐 - 只获取一次时间 current_time=$(date) for i in {1..100}; do echo "$current_time: 处理项目 $i" done -
使用内置字符串操作代替外部命令:
bash复制# 不推荐 - 使用cut for line in "$data"; do field=$(echo "$line" | cut -d: -f1) done # 推荐 - 使用bash内置字符串操作 for line in "$data"; do field="${line%%:*}" done -
避免在循环中频繁操作文件:
bash复制# 不推荐 - 每次循环都追加写入 for item in "${items[@]}"; do echo "$item" >> output.txt done # 推荐 - 收集输出后一次性写入 { for item in "${items[@]}"; do echo "$item" done } > output.txt
专业建议:在处理大量数据时,考虑使用更高效的工具如awk或专门的编程语言,shell循环适合中小规模数据处理
4. case语句与高级控制结构
4.1 case语句深度解析
case语句提供了一种更清晰的方式来实现多路分支,比多个if-elif语句更易读和维护。基本语法:
bash复制case "$变量" in
模式1)
# 匹配模式1时执行的命令
;;
模式2|模式3)
# 匹配模式2或模式3时执行的命令
;;
*)
# 默认情况执行的命令
;;
esac
系统服务管理脚本实例
bash复制#!/bin/bash
# 多功能服务管理脚本
SERVICE="${1:-nginx}"
ACTION="${2:-status}"
case "$ACTION" in
start|restart|stop|reload)
echo "执行: systemctl $ACTION $SERVICE"
systemctl "$ACTION" "$SERVICE"
;;
status)
systemctl status "$SERVICE" --no-pager
;;
enable|disable)
echo "执行: systemctl $ACTION $SERVICE"
systemctl "$ACTION" "$SERVICE"
;;
configtest)
if [ "$SERVICE" = "nginx" ]; then
nginx -t
elif [ "$SERVICE" = "apache2" ]; then
apache2ctl configtest
else
echo "错误:服务 $SERVICE 不支持配置测试"
exit 1
fi
;;
*)
echo "用法: $0 [服务名] [操作]"
echo "可用操作: start|stop|restart|reload|status|enable|disable|configtest"
exit 1
;;
esac
这个脚本展示了case语句的典型应用场景:
- 处理多个相似操作(start/stop/restart等)可以合并到一个分支
- 为特定操作(status/configtest)提供定制处理
- 使用*分支处理无效输入并显示帮助信息
- 支持默认参数(${1:-default}语法)
基于系统版本的差异化配置
bash复制#!/bin/bash
# 根据Debian版本执行不同配置
source /etc/os-release
case "$VERSION_ID" in
10)
echo "检测到Debian 10 (Buster)"
# Buster特定配置
PHP_VERSION="7.3"
;;
11)
echo "检测到Debian 11 (Bullseye)"
# Bullseye特定配置
PHP_VERSION="7.4"
;;
12)
echo "检测到Debian 12 (Bookworm)"
# Bookworm特定配置
PHP_VERSION="8.2"
;;
*)
echo "警告:未知Debian版本 $VERSION_ID,使用默认配置"
PHP_VERSION="7.4"
;;
esac
echo "配置PHP版本为 $PHP_VERSION"
# 后续使用$PHP_VERSION进行安装配置
此例展示了如何根据系统版本选择不同的配置。通过读取/etc/os-release文件获取系统信息,case语句匹配不同版本号执行相应操作。
4.2 模式匹配高级技巧
case语句的模式匹配支持一些高级特性:
通配符模式
bash复制case "$filename" in
*.tar.gz|*.tgz)
echo "处理gzip压缩的tar文件"
tar -xzf "$filename"
;;
*.tar.bz2|*.tbz)
echo "处理bzip2压缩的tar文件"
tar -xjf "$filename"
;;
*.zip)
echo "处理zip文件"
unzip "$filename"
;;
*)
echo "不支持的文件类型: $filename"
;;
esac
字符类模式
bash复制case "$input" in
[Yy]|[Yy][Ee][Ss])
echo "用户选择了是"
;;
[Nn]|[Nn][Oo])
echo "用户选择了否"
;;
*)
echo "无效输入"
;;
esac
正则表达式模式(Bash 4.0+)
bash复制# 需要Bash 4.0及以上版本
shopt -s extglob
case "$email" in
+([[:alnum:]_.-])@+([[:alnum:]_.-]) )
echo "有效的电子邮件格式"
;;
*)
echo "无效的电子邮件格式"
;;
esac
4.3 子shell与代码块
子shell和代码块是组织复杂控制流的强大工具。
子shell应用实例
bash复制#!/bin/bash
# 使用子shell进行环境隔离
CONFIG_FILE="/etc/app.conf"
# 在子shell中尝试修改配置
(
echo "在子shell中修改配置"
sed -i 's/DebugMode=on/DebugMode=off/' "$CONFIG_FILE"
echo "子shell中的配置值:"
grep "DebugMode" "$CONFIG_FILE"
)
# 主shell中的配置值不受影响
echo "主shell中的配置值:"
grep "DebugMode" "$CONFIG_FILE"
# 使用子shell捕获输出
CONFIG_CONTENT=$(cat "$CONFIG_FILE")
echo "配置文件内容长度: ${#CONFIG_CONTENT} 字符"
子shell特性:
- 在单独进程中执行
- 环境变量和目录更改不会影响父shell
- 适合隔离有风险的修改操作
- 可用于捕获命令输出
代码块应用实例
bash复制#!/bin/bash
# 使用代码块组织相关操作
{
echo "开始数据库维护操作"
date
echo "-------------------"
# 执行数据库备份
mysqldump --all-databases > /backups/full_dump.sql
backup_size=$(du -h /backups/full_dump.sql | awk '{print $1}')
# 优化表
mysqlcheck --optimize --all-databases
echo "备份完成,大小: $backup_size"
date
} >> /var/log/db_maintenance.log 2>&1
echo "数据库维护已完成,详情见日志"
代码块特性:
- 将多个命令组织为逻辑单元
- 可以整体重定向输入输出
- 提升代码可读性
- 变量修改会影响当前shell环境
4.4 函数中的控制结构
函数可以封装复杂的控制逻辑,提高脚本的模块化和重用性。
带参数检查的函数
bash复制#!/bin/bash
# 带参数验证的函数示例
# 创建系统用户并设置家目录
create_user() {
local username="$1"
local home_dir="${2:-/home/$username}"
# 参数验证
if [ -z "$username" ]; then
echo "错误:必须指定用户名"
return 1
fi
# 检查用户是否已存在
if id "$username" &>/dev/null; then
echo "警告:用户 $username 已存在"
return 2
fi
# 创建用户
echo "正在创建用户 $username,家目录 $home_dir"
useradd -m -d "$home_dir" -s /bin/bash "$username"
# 检查是否创建成功
if [ $? -eq 0 ]; then
echo "用户 $username 创建成功"
return 0
else
echo "错误:创建用户 $username 失败"
return 3
fi
}
# 使用函数
create_user "webadmin" "/srv/webadmin"
case $? in
0) echo "操作成功完成" ;;
1) echo "参数错误" ;;
2) echo "用户已存在" ;;
3) echo "创建失败" ;;
esac
这个例子展示了:
- 函数参数处理和默认值设置
- 输入验证和错误返回
- 使用局部变量避免命名冲突
- 通过返回值传递状态
- 调用者使用case语句处理不同返回状态
递归函数实例
bash复制#!/bin/bash
# 递归计算阶乘的示例
factorial() {
local n="$1"
# 基本情况
if [ "$n" -le 1 ]; then
echo 1
return
fi
# 递归情况
local prev=$(factorial $((n - 1)))
echo $((n * prev))
}
# 计算5的阶乘
result=$(factorial 5)
echo "5! = $result"
递归函数注意事项:
- 必须有明确的终止条件
- 每次递归应使问题规模减小
- 在shell中递归深度有限制(可通过ulimit -s查看和修改)
- 不适合解决大规模问题
专业提示:在shell脚本中,过度复杂的逻辑建议使用专门的编程语言实现,shell更适合作为胶水语言整合各种工具
5. 实战案例与最佳实践
5.1 系统自动化维护脚本
下面是一个综合运用各种控制结构的系统维护脚本,适合在Debian服务器上定期执行:
bash复制#!/bin/bash
# 综合系统维护脚本
LOG_FILE="/var/log/system_maintenance.log"
ERROR_FILE="/var/log/system_errors.log"
THRESHOLD=90 # 磁盘使用率告警阈值
# 初始化日志文件
{
echo "========================================"
echo "系统维护开始于: $(date)"
echo "主机名: $(hostname)"
echo "========================================"
} > "$LOG_FILE"
# 错误处理函数
log_error() {
echo "[错误] $(date): $1" >> "$ERROR_FILE"
echo "$1" >&2
}
# 检查磁盘空间
check_disk_space() {
echo "检查磁盘空间使用情况..." >> "$LOG_FILE"
df -h | grep -v "^tmpfs" | while read -r line
do
mount_point=$(echo "$line" | awk '{print $6}')
use_percent=$(echo "$line" | awk '{print $5}' | tr -d '%')
if [ "$use_percent" -ge "$THRESHOLD" ]; then
msg="警告: $mount_point 使用率 ${use_percent}% 超过阈值 ${THRESHOLD}%"
log_error "$msg"
echo "$msg" >> "$LOG_FILE"
else
echo "正常: $mount_point 使用率 ${use_percent}%" >> "$LOG_FILE"
fi
done
}
# 清理旧日志文件
clean_old_logs() {
echo "清理旧的日志文件..." >> "$LOG_FILE"
LOG_DIRS=("/var/log" "/opt/app/logs")
for dir in "${LOG_DIRS[@]}"
do
if [ -d "$dir" ]; then
find "$dir" -type f -name "*.log.*" -mtime +30 -exec rm -v {} \; >> "$LOG_FILE" 2>&1
count=$(find "$dir" -type f -name "*.log.*" -mtime +30 | wc -l)
echo "在 $dir 中清理了 $count 个旧日志文件" >> "$LOG_FILE"
else
echo "跳过不存在的目录: $dir" >> "$LOG_FILE"
fi
done
}
# 更新系统软件包
update_packages() {
echo "检查系统更新..." >> "$LOG_FILE"
# 检查是否需要更新
apt-get update >> "$LOG_FILE" 2>&1
updates_available=$(apt-get -s upgrade | grep -c "^Inst")
if [ "$updates_available" -gt 0 ]; then
echo "发现 $updates_available 个可用更新,正在安装..." >> "$LOG_FILE"
# 执行安全更新
apt-get upgrade --only-upgrade -y >> "$LOG_FILE" 2>&1
if [ $? -ne 0 ]; then
log_error "系统更新失败"
else
echo "系统更新成功完成" >> "$LOG_FILE"
fi
else
echo "系统已经是最新状态" >> "$LOG_FILE"
fi
}
# 检查关键服务状态
check_services() {
echo "检查关键服务状态..." >> "$LOG_FILE"
SERVICES=("nginx" "mysql" "ssh" "cron")
for service in "${SERVICES[@]}"
do
if systemctl is-active --quiet "$service"; then
echo "服务 $service 正在运行" >> "$LOG_FILE"
else
log_error "服务 $service 未运行"
echo "尝试启动 $service..." >> "$LOG_FILE"
systemctl start "$service" >> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
echo "成功启动 $service" >> "$LOG_FILE"
else
log_error "无法启动 $service"
fi
fi
done
}
# 执行所有维护任务
main() {
check_disk_space
clean_old_logs
update_packages
check_services
echo "========================================" >> "$LOG_FILE"
echo "系统维护完成于: $(date)" >> "$LOG_FILE"
echo "========================================" >> "$LOG_FILE"
# 发送摘要邮件
errors=$(wc -l < "$ERROR_FILE")
if [ "$errors" -gt 0 ]; then
mail -s "系统维护报告(有错误)" admin@example.com < "$LOG_FILE"
else
mail -s "系统维护报告(正常)" admin@example.com < "$LOG_FILE"
fi
}
# 执行主函数
main
这个脚本实现了:
- 全面的磁盘空间检查
- 自动清理旧日志文件
- 系统软件包更新
- 关键服务状态监控和自动恢复
- 详细的日志记录和错误报告
- 结果邮件通知
5.2 安全备份解决方案
下面是一个使用各种控制结构实现的增强型备份脚本:
bash复制#!/bin/bash
# 安全备份脚本
BACKUP_ROOT="/backups"
CONFIG_FILE="/etc/backup_config.conf"
MAX_BACKUPS=5
COMPRESSION="gz" # gz, bz2, xz
ENCRYPT_KEY="/etc/backup.key"
# 加载配置文件
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
else
echo "警告: 配置文件 $CONFIG_FILE 不存在,使用默认设置" >&2
fi
# 验证加密密钥
if [ ! -f "$ENCRYPT_KEY" ]; then
echo "错误: 加密密钥 $ENCRYPT_KEY 不存在" >&2
exit 1
fi
# 创建备份目录
BACKUP_DIR="$BACKUP_ROOT/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR" || {
echo "错误: 无法创建备份目录 $BACKUP_DIR" >&2
exit 1
}
# 备份数据库函数
backup_database() {
local db_type="$1"
local db_name="$2"
local output_file="$BACKUP_DIR/${db_type}_${db_name}.sql"
case "$db_type" in
mysql)
if ! command -v mysqldump >/dev/null; then
echo "错误: mysqldump 命令不可用" >&2
return 1
fi
echo "备份MySQL数据库: $db_name"
mysqldump --single-transaction "$db_name" > "${output_file}"
;;
postgresql)
if ! command -v pg_dump >/dev/null; then
echo "错误: pg_dump 命令不可用" >&2
return 1
fi
echo "备份PostgreSQL数据库: $db_name"
pg_dump "$db_name" > "${output_file}"
;;
*)
echo "错误: 不支持的数据库类型 $db_type" >&2
return 1
;;
esac
if [ $? -ne 0 ]; then
echo "数据库备份失败" >&2