思路:
备份前会自动校验是否有需要清理的文件
两种使用方式:
./mysql_backup.sh 进入终端交互模式
./mysql_backup.sh 参数 ,案例:./mysql_backup.sh 1 ,执行全量备份
后台运行,查看日志案例:
bash
# 后台运行全量备份,日志输出到文件
nohup ./mysql_backup.sh 1 > mysql_backup.log 2>&1 &
bash
touch mysql_backup.sh && chmod +x mysql_backup.sh
bash
#!/bin/bash
# MySQL备份恢复脚本
# 支持全量备份、部分库备份、备份恢复、自动清理
# Author: System Administrator
# Version: 1.0
# ===============================================
# 配置参数 - 请根据实际环境修改
# ===============================================
# MySQL连接配置
MYSQL_USER="root" # MySQL用户名
MYSQL_PASS="cnmsb123" # MySQL密码
MYSQL_HOST="localhost" # MySQL主机地址,默认本机
MYSQL_PORT="3306" # MySQL端口,默认3306
# 备份配置
BACKUP_DIR="/opt" # 备份文件存储目录
RETENTION_DAYS=7 # 备份文件保留天数,超过此天数的备份将被自动清理
# 部分备份排除数据库配置
# 在进行部分备份时,以下数据库将被排除(不进行备份)
# 可以根据需要修改此列表,多个数据库用逗号分隔,例如:a,b,c #代表排除a,b,c再备份
EXCLUDE_DATABASES=""
# 备份选项配置
MYSQLDUMP_OPTIONS="--single-transaction --routines --triggers --set-gtid-purged=OFF" # mysqldump额外选项
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m' # No Color
# 动画字符
SPINNER='|/-\'
# 打印带颜色的文本
print_color() {
local color=$1
local text=$2
echo -e "${color}${text}${NC}"
}
# 清屏并移动光标到顶部
clear_screen() {
clear
printf "\033[H"
}
# 获取终端尺寸
get_terminal_size() {
TERM_COLS=$(tput cols 2>/dev/null || echo 80)
TERM_ROWS=$(tput lines 2>/dev/null || echo 24)
}
# 居中显示文本
center_text() {
local text="$1"
local color="$2"
local padding=$(( (TERM_COLS - ${#text}) / 2 ))
printf "%*s" $padding ""
print_color "$color" "$text"
}
# 绘制分隔线
draw_line() {
local char="${1:-═}"
local color="${2:-$CYAN}"
printf "${color}"
for ((i=1; i<=TERM_COLS; i++)); do
printf "$char"
done
printf "${NC}\n"
}
# 绘制边框
draw_border() {
local title="$1"
draw_line "═" "$CYAN"
echo ""
center_text "$title" "$WHITE"
echo ""
draw_line "═" "$CYAN"
}
# 全屏标题
print_title() {
clear_screen
get_terminal_size
echo ""
draw_border "MySQL 备份恢复管理工具 v1.0"
echo ""
# 显示系统信息
center_text "🖥️ 系统: $(uname -s) $(uname -r)" "$CYAN"
center_text "📅 时间: $(date '+%Y-%m-%d %H:%M:%S')" "$CYAN"
center_text "👤 用户: $(whoami)@$(hostname)" "$CYAN"
echo ""
draw_line "─" "$BLUE"
}
# 高级进度条动画
show_progress() {
local message=$1
local process_pid=$2
local spinner_chars="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
local delay=0.1
local spin_i=0
echo -n -e "${YELLOW}${message}${NC} "
# 如果提供了进程PID,监控进程状态
if [ -n "$process_pid" ]; then
while kill -0 $process_pid 2>/dev/null; do
printf "\b${CYAN}%s${NC}" "${spinner_chars:spin_i++%${#spinner_chars}:1}"
sleep $delay
done
else
# 简单的固定时间动画
for i in {1..30}; do
printf "\b${CYAN}%s${NC}" "${spinner_chars:spin_i++%${#spinner_chars}:1}"
sleep $delay
done
fi
echo -e "\b${GREEN}✓${NC}"
}
# 计时器函数
start_timer() {
START_TIME=$(date +%s)
}
end_timer() {
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
local hours=$((DURATION / 3600))
local minutes=$(((DURATION % 3600) / 60))
local seconds=$((DURATION % 60))
if [ $hours -gt 0 ]; then
echo "${hours}小时${minutes}分${seconds}秒"
elif [ $minutes -gt 0 ]; then
echo "${minutes}分${seconds}秒"
else
echo "${seconds}秒"
fi
}
# 检查MySQL连接
check_mysql_connection() {
print_color $BLUE "🔍 检查MySQL连接状态..."
if mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SELECT 1;" >/dev/null 2>&1; then
print_color $GREEN "✅ MySQL连接成功"
print_color $CYAN " 连接信息: ${MYSQL_USER}@${MYSQL_HOST}:${MYSQL_PORT}"
return 0
else
print_color $RED "❌ MySQL连接失败,请检查连接配置"
print_color $YELLOW " 主机: ${MYSQL_HOST}:${MYSQL_PORT}"
print_color $YELLOW " 用户: ${MYSQL_USER}"
exit 1
fi
}
# 创建备份目录
create_backup_dir() {
if [ ! -d "$BACKUP_DIR" ]; then
print_color $YELLOW "📁 创建备份目录: $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
fi
}
# 清理过期备份
cleanup_old_backups() {
print_color $BLUE "🧹 清理 ${RETENTION_DAYS} 天前的备份文件..."
local deleted_count=0
while IFS= read -r -d '' file; do
rm -f "$file"
deleted_count=$((deleted_count + 1))
print_color $YELLOW " 删除: $(basename "$file")"
done < <(find "$BACKUP_DIR" -name "*.sql" -type f -mtime +$RETENTION_DAYS -print0 2>/dev/null)
# 清理过期的gz文件
while IFS= read -r -d '' file; do
rm -f "$file"
deleted_count=$((deleted_count + 1))
print_color $YELLOW " 删除: $(basename "$file")"
done < <(find "$BACKUP_DIR" -name "*.gz" -type f -mtime +$RETENTION_DAYS -print0 2>/dev/null)
if [ $deleted_count -eq 0 ]; then
print_color $GREEN "✅ 没有需要清理的过期备份文件"
else
print_color $GREEN "✅ 已清理 ${deleted_count} 个过期备份文件"
fi
}
# 压缩备份文件
compress_backup() {
local sql_file=$1
local gz_file="${sql_file}.gz"
print_color $BLUE "📦 压缩备份文件..."
if command -v gzip >/dev/null 2>&1; then
if gzip "$sql_file"; then
local size=$(du -h "$gz_file" | cut -f1)
print_color $GREEN "✅ 压缩完成!"
print_color $GREEN " 压缩文件: $(basename "$gz_file")"
print_color $GREEN " 文件大小: ${size}"
echo "$gz_file"
else
print_color $RED "❌ 压缩失败"
return 1
fi
else
print_color $YELLOW "⚠️ 系统未安装gzip命令,跳过压缩"
echo "$sql_file"
fi
}
# 获取所有数据库列表(排除系统数据库)
get_all_databases() {
mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASS" -e "SHOW DATABASES;" 2>/dev/null | grep -Ev "^(Database|information_schema|performance_schema|mysql|sys)$"
}
# 全量备份
full_backup() {
# 备份前先执行清理
print_color $BLUE "🧹 备份前清理过期文件..."
cleanup_old_backups
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${BACKUP_DIR}/full_backup_${timestamp}.sql"
print_color $BLUE "🚀 开始全量备份..."
print_color $CYAN " 备份文件: $(basename "$backup_file")"
# 开始计时
start_timer
# 后台执行备份命令
mysqldump -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASS" --all-databases $MYSQLDUMP_OPTIONS > "$backup_file" 2>/dev/null &
local backup_pid=$!
# 显示进度条
show_progress "正在执行全量备份" $backup_pid
# 等待备份完成并检查结果
wait $backup_pid
local backup_result=$?
# 结束计时
local elapsed_time=$(end_timer)
if [ $backup_result -eq 0 ] && [ -s "$backup_file" ]; then
print_color $GREEN "✅ 全量备份完成!耗时: ${elapsed_time}"
# 压缩备份文件
local final_file=$(compress_backup "$backup_file")
if [ $? -eq 0 ]; then
print_color $GREEN " 保存路径: ${final_file}"
else
print_color $GREEN " 保存路径: ${backup_file}"
fi
else
print_color $RED "❌ 全量备份失败,耗时: ${elapsed_time}"
rm -f "$backup_file"
return 1
fi
}
# 部分库备份(排除预设的数据库)
partial_backup() {
# 检查是否配置了排除数据库
if [ -z "$EXCLUDE_DATABASES" ]; then
print_color $RED "❌ 未配置排除数据库列表"
print_color $YELLOW " 请在脚本顶部的 EXCLUDE_DATABASES 变量中配置要排除的数据库"
print_color $YELLOW " 示例: EXCLUDE_DATABASES=\"test_db,log_db,temp_db\""
return 1
fi
print_color $CYAN "📋 配置的排除数据库: $EXCLUDE_DATABASES"
# 将逗号分隔的字符串转换为数组
IFS=',' read -ra exclude_array <<< "$EXCLUDE_DATABASES"
# 获取所有数据库并排除指定的
local all_dbs=$(get_all_databases)
local backup_dbs=""
if [ -z "$all_dbs" ]; then
print_color $RED "❌ 未找到任何用户数据库"
return 1
fi
print_color $BLUE "📊 分析数据库列表..."
print_color $CYAN " 发现用户数据库: $(echo $all_dbs | tr '\n' ' ')"
# 处理排除逻辑
local excluded_dbs=""
for db in $all_dbs; do
local skip=false
for exclude_db in "${exclude_array[@]}"; do
# 去除可能的空格和特殊字符
exclude_db=$(echo "$exclude_db" | xargs | tr -d '\r\n')
if [ "$db" = "$exclude_db" ]; then
skip=true
excluded_dbs="$excluded_dbs $db"
break
fi
done
if [ "$skip" = false ]; then
backup_dbs="$backup_dbs $db"
fi
done
# 显示处理结果
if [ -n "$excluded_dbs" ]; then
print_color $YELLOW " 已排除数据库:$excluded_dbs"
fi
print_color $GREEN " 将备份数据库:$backup_dbs"
if [ -z "$backup_dbs" ]; then
print_color $RED "❌ 排除后没有需要备份的数据库"
return 1
fi
echo ""
echo -n -e "${YELLOW}确认执行备份?(y/N): ${NC}"
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
print_color $YELLOW "⏹️ 备份已取消"
return 0
fi
# 备份前先执行清理
print_color $BLUE "🧹 备份前清理过期文件..."
cleanup_old_backups
local timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_file="${BACKUP_DIR}/partial_backup_${timestamp}.sql"
print_color $BLUE "🚀 开始部分库备份..."
print_color $CYAN " 备份文件: $(basename "$backup_file")"
# 开始计时
start_timer
# 后台执行备份命令
mysqldump -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASS" --databases $backup_dbs $MYSQLDUMP_OPTIONS > "$backup_file" 2>/dev/null &
local backup_pid=$!
# 显示进度条
show_progress "正在执行部分库备份" $backup_pid
# 等待备份完成并检查结果
wait $backup_pid
local backup_result=$?
# 结束计时
local elapsed_time=$(end_timer)
if [ $backup_result -eq 0 ] && [ -s "$backup_file" ]; then
print_color $GREEN "✅ 部分库备份完成!耗时: ${elapsed_time}"
# 压缩备份文件
local final_file=$(compress_backup "$backup_file")
if [ $? -eq 0 ]; then
print_color $GREEN " 保存路径: ${final_file}"
else
print_color $GREEN " 保存路径: ${backup_file}"
fi
else
print_color $RED "❌ 部分库备份失败,耗时: ${elapsed_time}"
rm -f "$backup_file"
return 1
fi
}
# 列出备份文件
list_backups() {
print_color $BLUE "📋 可用的备份文件:"
echo ""
local backup_files=($(find "$BACKUP_DIR" \( -name "*.sql" -o -name "*.gz" \) -type f -printf "%T@ %p\n" 2>/dev/null | sort -rn | cut -d' ' -f2-))
if [ ${#backup_files[@]} -eq 0 ]; then
print_color $YELLOW "⚠️ 没有找到备份文件"
return 1
fi
local index=1
for file in "${backup_files[@]}"; do
local filename=$(basename "$file")
local size=$(du -h "$file" 2>/dev/null | cut -f1)
local date=$(date -r "$file" "+%Y-%m-%d %H:%M:%S" 2>/dev/null)
printf "${CYAN}%2d.${NC} %-40s ${GREEN}%8s${NC} ${YELLOW}%s${NC}\n" "$index" "$filename" "$size" "$date"
index=$((index + 1))
done
return 0
}
# 恢复备份
restore_backup() {
if ! list_backups; then
return 1
fi
echo ""
echo -n -e "${YELLOW}请选择要恢复的备份文件编号: ${NC}"
read -r selection
if ! [[ "$selection" =~ ^[0-9]+$ ]]; then
print_color $RED "❌ 请输入有效的数字"
return 1
fi
local backup_files=($(find "$BACKUP_DIR" \( -name "*.sql" -o -name "*.gz" \) -type f -printf "%T@ %p\n" 2>/dev/null | sort -rn | cut -d' ' -f2-))
local selected_index=$((selection - 1))
if [ $selected_index -lt 0 ] || [ $selected_index -ge ${#backup_files[@]} ]; then
print_color $RED "❌ 选择的编号超出范围"
return 1
fi
local backup_file="${backup_files[$selected_index]}"
local filename=$(basename "$backup_file")
print_color $YELLOW "⚠️ 警告:恢复操作将覆盖现有数据库!"
print_color $CYAN "选择的备份文件: $filename"
echo -n -e "${RED}确认执行恢复操作?(y/N): ${NC}"
read -r confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
print_color $YELLOW "⏹️ 恢复已取消"
return 0
fi
print_color $BLUE "🔄 开始恢复数据库..."
print_color $CYAN " 恢复文件: $filename"
show_progress 5 "正在执行数据库恢复"
# 如果是gz文件,先解压
local actual_file="$backup_file"
if [[ "$backup_file" == *.gz ]]; then
print_color $BLUE "📦 解压备份文件..."
local temp_sql="${backup_file%.*}"
if gunzip -c "$backup_file" > "$temp_sql"; then
actual_file="$temp_sql"
else
print_color $RED "❌ 解压失败"
return 1
fi
fi
if mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASS" < "$actual_file" 2>/dev/null; then
print_color $GREEN "✅ 数据库恢复完成!"
# 如果是临时解压的文件,删除它
if [[ "$backup_file" == *.gz ]] && [ "$actual_file" != "$backup_file" ]; then
rm -f "$actual_file"
fi
else
print_color $RED "❌ 数据库恢复失败"
# 如果是临时解压的文件,删除它
if [[ "$backup_file" == *.gz ]] && [ "$actual_file" != "$backup_file" ]; then
rm -f "$actual_file"
fi
return 1
fi
}
# 显示帮助信息
show_help() {
print_color $CYAN "用法:"
print_color $WHITE " $0 # 交互式模式"
print_color $WHITE " $0 [选项] # 命令行模式"
echo ""
print_color $CYAN "选项:"
print_color $WHITE " 1 # 全量备份"
print_color $WHITE " 2 # 部分库备份(排除指定数据库)"
print_color $WHITE " 3 # 恢复备份"
print_color $WHITE " 4 # 查看备份文件"
print_color $WHITE " 5 # 退出"
print_color $WHITE " -h, --help # 显示此帮助信息"
echo ""
print_color $CYAN "示例:"
print_color $WHITE " $0 1 # 直接执行全量备份"
print_color $WHITE " $0 2 # 直接执行部分库备份"
print_color $WHITE " $0 4 # 查看备份文件列表"
echo ""
}
# 显示状态栏
show_status_bar() {
echo ""
draw_line "─" "$BLUE"
printf "${CYAN}状态: ${GREEN}✅ 已连接${NC}"
printf "${CYAN} | 备份目录: ${YELLOW}${BACKUP_DIR}${NC}"
printf "${CYAN} | 保留天数: ${YELLOW}${RETENTION_DAYS}天${NC}\n"
draw_line "─" "$BLUE"
}
# 显示菜单选项
show_menu_item() {
local num=$1
local icon=$2
local text=$3
local desc=$4
# 计算总宽度为50个字符,确保在边框内对齐
printf " ${WHITE}[${CYAN}%s${WHITE}]${NC} %s ${WHITE}%-16s${NC} ${YELLOW}%-20s${NC}" "$num" "$icon" "$text" "$desc"
}
# 全屏显示菜单
show_menu() {
echo ""
echo ""
center_text "📋 主菜单 - 请选择操作" "$BLUE"
echo ""
echo ""
# 创建菜单框 - 使用固定宽度来确保对齐
local menu_width=56
local menu_start=$(( (TERM_COLS - menu_width) / 2 ))
# 顶部边框
printf "%*s${CYAN}┌" $menu_start ""
for ((i=1; i<=menu_width-2; i++)); do printf "─"; done
printf "┐${NC}\n"
# 空行
printf "%*s${CYAN}│" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
# 菜单项
printf "%*s${CYAN}│${NC}" $menu_start ""
show_menu_item "1" "🗃️ " "全量备份" "备份所有数据库"
printf "${CYAN}│${NC}\n"
printf "%*s${CYAN}│${NC}" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
printf "%*s${CYAN}│${NC}" $menu_start ""
show_menu_item "2" "📦" "部分库备份" "排除指定数据库后备份"
printf "${CYAN}│${NC}\n"
printf "%*s${CYAN}│${NC}" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
printf "%*s${CYAN}│${NC}" $menu_start ""
show_menu_item "3" "🔄" "恢复备份" "从备份文件恢复数据库"
printf "${CYAN}│${NC}\n"
printf "%*s${CYAN}│${NC}" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
printf "%*s${CYAN}│${NC}" $menu_start ""
show_menu_item "4" "📋" "查看备份" "列出所有备份文件"
printf "${CYAN}│${NC}\n"
printf "%*s${CYAN}│${NC}" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
printf "%*s${CYAN}│${NC}" $menu_start ""
show_menu_item "5" "🚪" "退出程序" "安全退出备份工具"
printf "${CYAN}│${NC}\n"
# 空行
printf "%*s${CYAN}│" $menu_start ""
printf "%*s${CYAN}│${NC}\n" $((menu_width-1)) ""
# 底部边框
printf "%*s${CYAN}└" $menu_start ""
for ((i=1; i<=menu_width-2; i++)); do printf "─"; done
printf "┘${NC}\n"
echo ""
show_status_bar
echo ""
}
# 执行指定的操作
execute_operation() {
local choice=$1
case $choice in
1)
print_color $BLUE "🚀 执行全量备份..."
full_backup
;;
2)
print_color $BLUE "📦 执行部分库备份..."
partial_backup
;;
3)
print_color $BLUE "🔄 执行备份恢复..."
restore_backup
;;
4)
print_color $BLUE "📋 查看备份文件..."
list_backups
;;
5)
print_color $GREEN "👋 感谢使用MySQL备份恢复工具!"
exit 0
;;
*)
print_color $RED "❌ 无效选择: $choice"
print_color $YELLOW "请使用 1-5 之间的数字,或运行 $0 --help 查看帮助"
exit 1
;;
esac
}
# 显示输入提示
show_input_prompt() {
echo ""
center_text "请输入您的选择 [1-5]" "$YELLOW"
echo ""
printf "%*s${CYAN}➤ ${NC}" $(( (TERM_COLS - 10) / 2 )) ""
}
# 显示操作结果页面
show_result_page() {
local title="$1"
local status="$2"
clear_screen
get_terminal_size
echo ""
draw_border "$title"
echo ""
if [ "$status" = "success" ]; then
center_text "✅ 操作完成" "$GREEN"
else
center_text "❌ 操作失败" "$RED"
fi
echo ""
echo ""
center_text "按任意键返回主菜单..." "$CYAN"
}
# 交互式模式
interactive_mode() {
while true; do
clear_screen
print_title
show_menu
show_input_prompt
read -r choice
case "$choice" in
1|2|3|4|5)
clear_screen
print_title
execute_operation "$choice"
if [ "$choice" != "5" ]; then
if [ "$choice" != "4" ]; then
echo ""
echo ""
center_text "操作完成,按任意键返回主菜单..." "$CYAN"
read -r
else
echo ""
echo ""
center_text "按任意键返回主菜单..." "$CYAN"
read -r
fi
fi
;;
*)
clear_screen
print_title
echo ""
center_text "❌ 无效选择,请输入 1-5 之间的数字" "$RED"
echo ""
center_text "按任意键返回主菜单..." "$CYAN"
read -r
;;
esac
done
}
# 主函数
main() {
# 处理命令行参数
if [ $# -gt 0 ]; then
case "$1" in
-h|--help)
print_title
show_help
exit 0
;;
1|2|3|4|5)
# 命令行模式 - 直接执行指定操作
print_title
# 初始化检查
check_mysql_connection
create_backup_dir
# 执行指定操作
execute_operation "$1"
exit 0
;;
*)
print_color $RED "❌ 无效参数: $1"
echo ""
show_help
exit 1
;;
esac
fi
# 交互式模式
# 检查是否为root用户或有sudo权限
if [ "$EUID" -ne 0 ] && ! sudo -n true 2>/dev/null; then
print_color $YELLOW "⚠️ 建议使用root权限运行此脚本"
fi
print_title
# 初始化检查
check_mysql_connection
create_backup_dir
# 进入交互式模式
interactive_mode
}
# 信号处理
trap 'echo -e "\n${YELLOW}👋 程序已中断退出${NC}"; exit 1' INT TERM
# 运行主程序
main "$@"