本文目录导读:

Shell脚本实现跳过回滚策略通常用于自动化部署、数据库迁移或任务执行中,以下提供几种常见实现方式:
版本号控制策略
#!/bin/bash
# 版本文件存储位置
VERSION_FILE="/tmp/deploy_version.txt"
CURRENT_VERSION=$(cat $VERSION_FILE 2>/dev/null || echo "0")
# 定义执行函数,带版本标记
execute_step() {
local step_name="$1"
local step_version="$2"
local step_command="$3"
# 跳过小于当前版本的步骤
if [ "$step_version" -le "$CURRENT_VERSION" ]; then
echo "[SKIP] $step_name (version $step_version <= current $CURRENT_VERSION)"
return 0
fi
echo "[EXECUTE] $step_name (version $step_version)"
eval "$step_command"
if [ $? -eq 0 ]; then
echo "$step_version" > "$VERSION_FILE"
echo "[SUCCESS] $step_name completed"
else
echo "[FAILED] $step_name - rolling back to version $CURRENT_VERSION"
echo "$CURRENT_VERSION" > "$VERSION_FILE" # 回滚版本号
return 1
fi
}
# 使用示例
execute_step "备份数据库" 1 "echo 'Backup completed'"
execute_step "更新配置" 2 "echo 'Config updated'"
execute_step "重启服务" 3 "echo 'Service restarted'"
状态文件标记法
#!/bin/bash
STATUS_DIR="/tmp/deploy_status"
mkdir -p "$STATUS_DIR"
# 执行和跳过逻辑
run_or_skip() {
local step_id="$1"
local step_name="$2"
local step_func="$3"
# 检查是否已成功执行
if [ -f "$STATUS_DIR/$step_id.completed" ]; then
echo "[SKIP] $step_name - already completed"
return 0
fi
# 检查是否有回滚标记
if [ -f "$STATUS_DIR/$step_id.rollback" ]; then
echo "[ROLLBACK] $step_name - undoing changes"
if [ -f "$STATUS_DIR/$step_id.rollback_script" ]; then
source "$STATUS_DIR/$step_id.rollback_script"
rm -f "$STATUS_DIR/$step_id.rollback"
rm -f "$STATUS_DIR/$step_id.completed"
fi
return 0
fi
# 执行步骤
echo "[EXECUTE] $step_name"
if $step_func; then
touch "$STATUS_DIR/$step_id.completed"
return 0
else
echo "[FAIL] $step_name - creating rollback marker"
touch "$STATUS_DIR/$step_id.rollback"
return 1
fi
}
# 示例函数
step1_backup() {
echo "Performing backup..."
return 0
}
step2_update() {
echo "Updating application..."
# 模拟失败
if [ "$RANDOM" -gt 10000 ]; then
return 1
fi
return 0
}
# 执行流程
run_or_skip "001" "Backup Database" step1_backup
run_or_skip "002" "Update Application" step2_update
数据库迁移回滚策略
#!/bin/bash
DB_NAME="myapp"
MIGRATIONS_DIR="/opt/migrations"
# 记录已执行的迁移
get_executed_migrations() {
mysql -e "SELECT migration_name FROM schema_migrations ORDER BY id;" "$DB_NAME" 2>/dev/null
}
# 记录迁移执行状态
mark_migration_executed() {
local name="$1"
mysql -e "INSERT INTO schema_migrations (migration_name, executed_at) VALUES ('$name', NOW());" "$DB_NAME"
}
# 带跳过回滚的迁移执行
execute_migration() {
local migration_file="$1"
local migration_name=$(basename "$migration_file" .sql)
# 检查是否已执行
if get_executed_migrations | grep -q "$migration_name"; then
echo "[SKIP] $migration_name already executed"
return 0
fi
echo "[EXECUTE] Migration: $migration_name"
# 执行迁移(捕获错误)
if mysql "$DB_NAME" < "$migration_file" 2>/tmp/migration_error; then
mark_migration_executed "$migration_name"
echo "[SUCCESS] $migration_name executed"
else
echo "[FAILED] Migration failed: $(cat /tmp/migration_error)"
# 自动回滚(如果有对应的回滚文件)
local rollback_file="${migration_file%.sql}_rollback.sql"
if [ -f "$rollback_file" ]; then
echo "[ROLLBACK] Executing rollback..."
mysql "$DB_NAME" < "$rollback_file"
fi
return 1
fi
}
# 执行所有待处理的迁移
for migration in $(ls $MIGRATIONS_DIR/*.sql | sort); do
execute_migration "$migration" || break
done
配置文件驱动策略
#!/bin/bash
# config.yaml 格式示例
: '
steps:
- name: "备份"
id: backup
command: "echo backup"
rollback: "echo restore_backup"
- name: "更新"
id: update
command: "echo update"
rollback: "echo revert_update"
'
# 读取配置并执行
execute_with_skip_rollback() {
local config_file="$1"
local state_file="${2:-/tmp/deploy_state.txt}"
# 读取已完成的步骤
declare -A completed_steps
if [ -f "$state_file" ]; then
while IFS= read -r line; do
completed_steps["$line"]=1
done < "$state_file"
fi
# 使用 yq 解析 YAML (需要安装 yq)
local steps_count=$(yq e '.steps | length' "$config_file")
for ((i=0; i<steps_count; i++)); do
local step_name=$(yq e ".steps[$i].name" "$config_file")
local step_id=$(yq e ".steps[$i].id" "$config_file")
local step_cmd=$(yq e ".steps[$i].command" "$config_file")
local rollback_cmd=$(yq e ".steps[$i].rollback" "$config_file")
# 跳过已完成的步骤
if [ "${completed_steps[$step_id]}" = "1" ]; then
echo "[SKIP] $step_name already completed"
continue
fi
# 执行步骤
echo "[EXECUTE] $step_name"
if eval "$step_cmd"; then
echo "$step_id" >> "$state_file"
echo "[SUCCESS] $step_name"
else
echo "[FAILED] $step_name - executing rollback"
eval "$rollback_cmd" || echo "[WARN] Rollback failed for $step_name"
return 1
fi
done
}
# 使用
execute_with_skip_rollback "deploy_config.yaml"
最佳实践建议
状态存储
# 使用数据库记录状态
mysql -e "
CREATE TABLE IF NOT EXISTS deploy_steps (
id INT AUTO_INCREMENT PRIMARY KEY,
step_name VARCHAR(255),
status ENUM('completed', 'failed', 'rolled_back'),
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
"
异常处理
# 设置错误处理
set -eE
trap 'handle_error $LINENO' ERR
handle_error() {
local line=$1
echo "Error occurred at line $line"
# 执行全局回滚逻辑
}
幂等性设计
# 确保操作可以安全重复执行
safe_mkdir() {
local dir="$1"
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
fi
}
safe_symlink() {
local target="$1"
local link="$2"
if [ -L "$link" ] && [ "$(readlink "$link")" = "$target" ]; then
return 0 # 已存在且指向正确
fi
ln -sf "$target" "$link"
}
完整示例:部署脚本
#!/bin/bash
# 配置
DEPLOY_DIR="/opt/app"
BACKUP_DIR="/opt/backups"
STATE_FILE="/tmp/deploy_state.txt"
# 初始化状态
mkdir -p "$(dirname "$STATE_FILE")"
# 检查并跳过已完成的步骤
check_step() {
local step="$1"
if grep -q "^$step$" "$STATE_FILE" 2>/dev/null; then
echo "[SKIP] $step"
return 0
fi
return 1
}
# 标记步骤完成
mark_complete() {
local step="$1"
echo "$step" >> "$STATE_FILE"
}
# 带回滚的执行
execute_step() {
local step="$1"
local action="$2"
local rollback="$3"
check_step "$step" && return 0
echo "[EXECUTE] $step"
if eval "$action"; then
mark_complete "$step"
else
echo "[FAIL] Rolling back: $step"
eval "$rollback" || echo "[WARN] Rollback failed"
return 1
fi
}
# 部署步骤
execute_step "backup" \
"cp -r $DEPLOY_DIR $BACKUP_DIR/backup_$(date +%Y%m%d)" \
"rm -rf $BACKUP_DIR/backup_$(date +%Y%m%d)"
execute_step "stop_service" \
"systemctl stop myapp" \
"systemctl start myapp"
execute_step "update_files" \
"cp -r /tmp/new_version/* $DEPLOY_DIR/" \
"cp -r $BACKUP_DIR/backup_$(date +%Y%m%d)/* $DEPLOY_DIR/"
execute_step "start_service" \
"systemctl start myapp" \
"systemctl stop myapp"
echo "Deployment completed successfully"
这些策略可以根据实际需求组合使用,关键是要确保操作的幂等性和可恢复性。