sh数据同步备份脚本
生成目录文件并写入文件tmpfile.txt
powershell
Get-ChildItem -Path "." -Recurse -File | ForEach-Object { $_.FullName.Substring((Get-Item ".").FullName.Length + 1).Replace('\', '/') } | Out-File -FilePath "tmpfile.txt" -Encoding UTF8
bash
find . -type f -printf '%P\n' | sed 's|\\|/|g' > tmpfile.txt
linux执行备份脚本
#!/bin/bash
# 安全模式:遇到未定义变量或管道失败时退出
set -uo pipefail
show_usage() {
cat <<EOF
用法: $0 <文件列表> <源目录> <备份目录>
参数说明:
文件列表 : 包含相对路径的文本文件(一行一个,如 common/file.php)
源目录 : 要从中复制文件的根目录(必须存在且为目录)
备份目录 : 目标备份目录(必须不存在,或存在但为空)
示例:
$0 tmpfile.txt /www/wwwroot/app /backup/app_20251208
注意:
- 自动跳过源中不存在的文件
- 自动按原目录结构创建目标路径
- 备份目录非空时拒绝执行,防止误覆盖
- 自动清理文件列表中的 BOM 和 Windows 换行符(\r)
EOF
}
# === 参数校验 ===
if [[ $# -ne 3 ]]; then
echo "❌ 错误: 需要 3 个参数。" >&2
show_usage
exit 1
fi
FILE_LIST="$1"
SOURCE_ROOT="$2"
DEST_ROOT="$3"
# 标准化路径(解析符号链接,转为绝对路径)
if ! SOURCE_ROOT="$(realpath "$SOURCE_ROOT" 2>/dev/null)" || [[ -z "$SOURCE_ROOT" ]]; then
echo "❌ 错误: 无法解析源目录路径: $SOURCE_ROOT" >&2
exit 1
fi
if ! DEST_ROOT="$(realpath -m "$DEST_ROOT" 2>/dev/null)" || [[ -z "$DEST_ROOT" ]]; then
echo "❌ 错误: 无法解析备份目录路径: $DEST_ROOT" >&2
exit 1
fi
# === 文件与目录存在性校验 ===
if [[ ! -f "$FILE_LIST" ]]; then
echo "❌ 错误: 文件列表不存在: $FILE_LIST" >&2
exit 1
fi
if [[ ! -d "$SOURCE_ROOT" ]]; then
echo "❌ 错误: 源目录不存在或不是目录: $SOURCE_ROOT" >&2
exit 1
fi
# === 自动清理文件列表:去除 UTF-8 BOM 和 Windows \r,并回写到原文件 ===
echo "ℹ️ 清理文件列表中的 BOM 和 Windows 换行符..." >&2
{
# 读取内容,去除 BOM(仅第一行)和所有 \r
sed '1s/^\xEF\xBB\xBF//' "$FILE_LIST" | tr -d '\r'
} > "${FILE_LIST}.clean" && mv "${FILE_LIST}.clean" "$FILE_LIST"
# === 备份目录安全检查 ===
if [[ -e "$DEST_ROOT" ]]; then
if [[ ! -d "$DEST_ROOT" ]]; then
echo "❌ 错误: 备份路径已存在但不是目录: $DEST_ROOT" >&2
exit 1
fi
if [[ -n "$(ls -A "$DEST_ROOT" 2>/dev/null)" ]]; then
echo "❌ 错误: 备份目录非空!为防止数据覆盖,拒绝操作: $DEST_ROOT" >&2
exit 1
fi
else
echo "ℹ️ 备份目录不存在,将自动创建: $DEST_ROOT"
fi
# === 读取并预处理文件列表 ===
declare -a rel_paths=()
while IFS= read -r line || [[ -n "$line" ]]; do
# 跳过空行和注释(以 # 开头,允许前导空格)
if [[ -n "$line" ]] && ! [[ "$line" =~ ^[[:space:]]*# ]]; then
rel_paths+=("$line")
fi
done < "$FILE_LIST"
total="${#rel_paths[@]}"
if (( total == 0 )); then
echo "⚠️ 警告: 文件列表中没有有效路径(空文件或全是注释/空行)。" >&2
echo
echo "📊 操作完成!"
echo " 总文件数: 0"
echo " 成功: 0"
echo " 失败: 0"
echo "✅ 无操作完成。"
exit 0
fi
# === 初始化计数器 ===
success=0
failed=0
declare -a failed_list
# === 主处理循环 ===
for rel_path in "${rel_paths[@]}"; do
src="$SOURCE_ROOT/$rel_path"
dest="$DEST_ROOT/$rel_path"
dest_dir="$(dirname "$dest")"
if [[ -f "$src" ]]; then
if mkdir -p "$dest_dir" 2>/dev/null && cp "$src" "$dest" 2>/dev/null; then
((success++))
else
((failed++))
failed_list+=("$rel_path")
echo "⚠️ 处理失败: $rel_path" >&2
fi
else
((failed++))
failed_list+=("$rel_path")
echo "⚠️ 源文件不存在或非普通文件: $rel_path" >&2
fi
done
# === 输出汇总报告 ===
echo
echo "📊 操作完成!"
echo " 总文件数: $total"
echo " 成功: $success"
echo " 失败: $failed"
if (( failed > 0 )); then
echo
echo "❌ 以下 $failed 个文件处理失败:"
for fp in "${failed_list[@]}"; do
printf " - %s\n" "$fp"
done
exit 1
else
echo "✅ 所有文件备份成功!"
exit 0
fi