本文目录导读:

#!/bin/bash
#
# 文件名: batch_file_modifier.sh
# 描述: 批量修改文件的安全脚本,支持预览、备份、正则匹配与恢复
# 使用方法:
# 1. 先预览: bash batch_file_modifier.sh preview
# 2. 执行修改: bash batch_file_modifier.sh apply
# 3. 恢复备份: bash batch_file_modifier.sh restore
#
set -euo pipefail
# ==================== 配置区域 (请根据需求修改) ====================
# 目标目录 (默认为当前目录,建议修改为具体路径)
TARGET_DIR="."
# 备份目录 (自动创建)
BACKUP_DIR="${TARGET_DIR}/.batch_file_backup_$(date +%Y%m%d_%H%M%S)"
# 要匹配的文件模式 (支持通配符)
# "*.txt", "*.md", "*.sh", "config.*"
FILE_PATTERN="*.txt"
# 要执行的操作类型: rename / content / both
# rename - 重命名文件
# content - 修改文件内容
# both - 同时执行两种操作
OPERATION_TYPE="rename"
# -------- 重命名规则 (当 OPERATION_TYPE=rename 或 both 时生效) --------
# 使用 sed 正则表达式替换文件名
# 格式: "s/查找模式/替换模式/g"
RENAME_PATTERN="s/old_prefix/new_prefix/g"
# -------- 内容修改规则 (当 OPERATION_TYPE=content 或 both 时生效) --------
# 使用 sed 正则表达式替换文件内容
# 格式: "s/查找模式/替换模式/g"
CONTENT_PATTERN="s/old_text/new_text/g"
# 是否递归处理子目录 (true/false)
RECURSIVE="true"
# 是否显示详细日志 (true/false)
VERBOSE="true"
# ==================== 函数定义 ====================
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# 日志函数
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_debug() { [[ "$VERBOSE" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $1"; }
# 显示帮助信息
usage() {
echo "用法: $0 {preview|apply|restore|help}"
echo ""
echo "命令:"
echo " preview - 预览将要修改的文件 (不实际执行)"
echo " apply - 执行修改 (自动备份原文件)"
echo " restore - 从最近的备份恢复修改"
echo " help - 显示此帮助信息"
echo ""
echo "请先编辑脚本中的配置区域,然后运行 preview 确认无误后再执行 apply。"
exit 0
}
# 检查依赖
check_dependencies() {
local deps=("sed" "find" "cp" "mv" "mkdir" "date")
for cmd in "${deps[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
log_error "缺少必需命令: $cmd"
exit 1
fi
done
}
# 生成备份文件路径
get_backup_path() {
local original_path="$1"
# 将绝对路径转换为相对备份路径 (去除开头的 / )
local relative_path="${original_path#/}"
echo "${BACKUP_DIR}/${relative_path}"
}
# 创建备份
create_backup() {
local file_path="$1"
local backup_path
backup_path="$(get_backup_path "$file_path")"
# 确保备份目录存在
mkdir -p "$(dirname "$backup_path")"
# 复制文件保留权限和时间戳
cp -p "$file_path" "$backup_path" 2>/dev/null || cp "$file_path" "$backup_path"
log_debug "已备份: $file_path -> $backup_path"
}
# 安全重命名文件
safe_rename() {
local old_path="$1"
local new_path="$2"
if [[ "$old_path" == "$new_path" ]]; then
return 0
fi
# 检查目标是否已存在
if [[ -e "$new_path" ]]; then
log_warn "目标文件已存在,跳过: $new_path"
return 1
fi
# 确保目标目录存在
mkdir -p "$(dirname "$new_path")" 2>/dev/null || true
mv "$old_path" "$new_path" 2>/dev/null
if [[ $? -eq 0 ]]; then
log_info "重命名: $old_path -> $new_path"
return 0
else
log_error "重命名失败: $old_path"
return 1
fi
}
# 获取符合条件的文件列表
get_file_list() {
local find_args=()
find_args+=("$TARGET_DIR")
find_args+=("-type" "f")
find_args+=("-name" "$FILE_PATTERN")
if [[ "$RECURSIVE" != "true" ]]; then
find_args+=("-maxdepth" "1")
fi
# 忽略备份目录
find_args+=("!" "-path" "${TARGET_DIR}/.batch_file_backup_*")
# 按文件名排序,保证可预测的顺序
find "${find_args[@]}" 2>/dev/null | sort
}
# 处理重命名操作
process_rename() {
local file="$1"
local dir
local base
local new_base
dir=$(dirname "$file")
base=$(basename "$file")
new_base=$(echo "$base" | sed "$RENAME_PATTERN")
if [[ "$base" != "$new_base" ]]; then
local new_path="${dir}/${new_base}"
echo "$file|$new_path"
fi
}
修改操作
process_content() {
local file="$1"
local temp_file
temp_file=$(mktemp)
# 检查是否包含匹配模式 (先预览)
if grep -q "$(echo "$CONTENT_PATTERN" | sed 's/^s\/\(.*\)\/.*\/.*$/\1/')" "$file" 2>/dev/null; then
echo "$file"
fi
rm -f "$temp_file"
}
# 预览模式
preview_mode() {
log_info "===== 预览模式 ====="
log_info "目标目录: $TARGET_DIR"
log_info "文件模式: $FILE_PATTERN"
log_info "操作类型: $OPERATION_TYPE"
echo ""
local files
files=$(get_file_list)
if [[ -z "$files" ]]; then
log_warn "没有找到匹配的文件。"
return
fi
local count=0
while IFS= read -r file; do
case "$OPERATION_TYPE" in
rename)
local result
result=$(process_rename "$file")
if [[ -n "$result" ]]; then
IFS='|' read -r old_path new_path <<< "$result"
log_info "将重命名: $old_path"
log_info " ->: $new_path"
((count++))
fi
;;
content)
local result
result=$(process_content "$file")
if [[ -n "$result" ]]; then
log_info "将修改内容: $file"
((count++))
fi
;;
both)
log_info "文件: $file"
local rename_result
rename_result=$(process_rename "$file")
if [[ -n "$rename_result" ]]; then
IFS='|' read -r old_path new_path <<< "$rename_result"
log_info " 重命名: $old_path -> $new_path"
fi
if process_content "$file" >/dev/null; then
log_info " 内容将被修改"
fi
((count++))
;;
esac
done <<< "$files"
echo ""
if [[ $count -eq 0 ]]; then
log_info "没有文件需要修改。"
else
log_info "共 $count 个文件将被修改。"
log_warn "运行 '$0 apply' 来执行修改,或 '$0 restore' 恢复备份。"
fi
}
# 执行模式
apply_mode() {
log_info "===== 执行模式 ====="
log_info "目标目录: $TARGET_DIR"
log_info "文件模式: $FILE_PATTERN"
log_info "操作类型: $OPERATION_TYPE"
# 创建备份目录
mkdir -p "$BACKUP_DIR"
log_info "备份目录: $BACKUP_DIR"
local files
files=$(get_file_list)
if [[ -z "$files" ]]; then
log_warn "没有找到匹配的文件。"
return
fi
local modified_count=0
local error_count=0
while IFS= read -r file; do
log_debug "处理文件: $file"
# 先备份
create_backup "$file"
case "$OPERATION_TYPE" in
rename)
local result
result=$(process_rename "$file")
if [[ -n "$result" ]]; then
IFS='|' read -r old_path new_path <<< "$result"
if safe_rename "$old_path" "$new_path"; then
((modified_count++))
else
((error_count++))
fi
fi
;;
content)
if process_content "$file" >/dev/null; then
if sed -i "$CONTENT_PATTERN" "$file" 2>/dev/null; then
log_info "修改内容: $file"
((modified_count++))
else
log_error "内容修改失败: $file"
((error_count++))
fi
fi
;;
both)
# 先修改内容
if process_content "$file" >/dev/null; then
sed -i "$CONTENT_PATTERN" "$file" 2>/dev/null && \
log_info "修改内容: $file" || \
log_error "内容修改失败: $file"
fi
# 再重命名
local result
result=$(process_rename "$file")
if [[ -n "$result" ]]; then
IFS='|' read -r old_path new_path <<< "$result"
if safe_rename "$old_path" "$new_path"; then
((modified_count++))
else
((error_count++))
fi
fi
;;
esac
done <<< "$files"
echo ""
log_info "修改完成: 成功 $modified_count, 失败 $error_count"
log_info "备份位置: $BACKUP_DIR"
log_warn "如需恢复,请运行 '$0 restore'"
}
# 恢复模式
restore_mode() {
log_info "===== 恢复模式 ====="
# 查找最近的备份目录
local backup_dirs
backup_dirs=$(find "$TARGET_DIR" -maxdepth 1 -type d -name ".batch_file_backup_*" | sort -r)
if [[ -z "$backup_dirs" ]]; then
log_error "没有找到备份目录。"
exit 1
fi
# 选择最近的备份
local latest_backup
latest_backup=$(echo "$backup_dirs" | head -1)
log_info "使用备份: $latest_backup"
if [[ ! -d "$latest_backup" ]]; then
log_error "备份目录不存在: $latest_backup"
exit 1
fi
local count=0
# 遍历备份文件并恢复
while IFS= read -r backup_file; do
# 计算原始路径
local relative_path="${backup_file#${latest_backup}/}"
local original_path="${TARGET_DIR}/${relative_path}"
# 确保目标目录存在
mkdir -p "$(dirname "$original_path")" 2>/dev/null || true
# 恢复文件
cp -p "$backup_file" "$original_path" 2>/dev/null || cp "$backup_file" "$original_path"
log_info "已恢复: $original_path"
((count++))
done < <(find "$latest_backup" -type f)
log_info "恢复完成,共恢复 $count 个文件。"
}
# ==================== 主程序 ====================
main() {
# 检查依赖
check_dependencies
# 处理命令行参数
case "${1:-help}" in
preview)
preview_mode
;;
apply)
if [[ ! -d "$TARGET_DIR" ]]; then
log_error "目标目录不存在: $TARGET_DIR"
exit 1
fi
apply_mode
;;
restore)
restore_mode
;;
help|--help|-h)
usage
;;
*)
log_error "未知命令: $1"
usage
;;
esac
}
main "$@"
使用说明
配置脚本
打开脚本,修改 配置区域 的变量:
TARGET_DIR:目标目录路径FILE_PATTERN:要处理的文件通配符OPERATION_TYPE:选择rename、content或bothRENAME_PATTERN/CONTENT_PATTERN:sed 正则替换规则
先预览(重要)
bash batch_file_modifier.sh preview
这将显示所有将要修改的文件,但不会实际执行。
执行修改
bash batch_file_modifier.sh apply
脚本会自动在目标目录下创建备份文件夹 .batch_file_backup_时间戳。
恢复备份
如果修改出错:
bash batch_file_modifier.sh restore
脚本会自动找到最近的备份并恢复。
安全特性
- 自动备份:修改前自动备份原文件
- 预览模式:先查看影响范围再执行
- 错误处理:重名冲突会跳过并警告
- 可恢复:支持一键恢复所有修改
- 无侵入:不会修改备份目录内的文件
常见示例
重命名示例
# 将 "report_2023.txt" 改为 "report_2024.txt" RENAME_PATTERN="s/2023/2024/g" FILE_PATTERN="*.txt"
替换示例
# 将文件中的 "http://" 替换为 "https://" CONTENT_PATTERN="s|http://|https://|g" OPERATION_TYPE="content" FILE_PATTERN="*.html"