- 始终显示统计摘要(相同、不同、仅存在于 dir1、仅存在于 dir2)
- 加上
-v选项时,同时逐行显示每个文件的详细对比状态 - 保留两种模式:严格对比(路径 + MD5) 和 仅内容对比(忽略路径)
- 自动处理权限,并给出友好的运行提示
完整脚本:compare_dirs_full.sh
bash
#!/bin/bash
set -e
# 显示帮助信息
usage() {
cat <<EOF
用法: $0 [选项] <目录1> <目录2>
比较两个目录下所有文件的 MD5 值,递归处理子目录。
选项:
-v, --verbose 显示每个文件的详细对比状态(相同/不同/仅存在)
-c, --content-only 只比较文件内容(忽略文件名和路径)
-h, --help 显示此帮助信息
无论是否使用 -v,最后都会输出统计摘要。
示例:
$0 /root/var/www/html/upload /var/www/html/upload
$0 -v /root/var/www/html/upload /var/www/html/upload
$0 -c /root/var/www/html/upload /var/www/html/upload
EOF
exit 1
}
# 默认参数
verbose=false
content_only=false
# 解析选项
OPTS=$(getopt -o vch --long verbose,content-only,help -n "$0" -- "$@")
if [ $? != 0 ]; then usage; fi
eval set -- "$OPTS"
while true; do
case "$1" in
-v|--verbose) verbose=true; shift ;;
-c|--content-only) content_only=true; shift ;;
-h|--help) usage ;;
--) shift; break ;;
*) break ;;
esac
done
# 检查参数
if [ $# -ne 2 ]; then
echo "错误:需要两个目录作为参数"
usage
fi
dir1="$1"
dir2="$2"
# 验证目录存在
if [ ! -d "$dir1" ]; then
echo "错误:目录 '$dir1' 不存在"
exit 1
fi
if [ ! -d "$dir2" ]; then
echo "错误:目录 '$dir2' 不存在"
exit 1
fi
# 创建临时文件并设置清理
tmp1=$(mktemp)
tmp2=$(mktemp)
trap 'rm -f "$tmp1" "$tmp2"' EXIT
# 生成 MD5 清单:格式为 "MD5 相对路径"
generate_list() {
local dir="$1"
local out="$2"
(cd "$dir" && find . -type f -exec md5sum {} + | sed 's|^\./||' | sort -k2) > "$out"
}
echo "正在生成目录 '$dir1' 的 MD5 清单..."
generate_list "$dir1" "$tmp1"
echo "正在生成目录 '$dir2' 的 MD5 清单..."
generate_list "$dir2" "$tmp2"
# ----- 仅比较内容(忽略文件名) -----
if [ "$content_only" = true ]; then
echo "--- 仅比较文件内容(忽略文件名) ---"
# 提取 MD5 值排序后比较
diff -u <(awk '{print $1}' "$tmp1" | sort) <(awk '{print $1}' "$tmp2" | sort) \
| sed -e '/^---/d' -e '/^\+\+\+/d' -e '/^@@/d' || true
# 统计:交集(相同MD5)、仅1、仅2
comm -12 <(awk '{print $1}' "$tmp1" | sort) <(awk '{print $1}' "$tmp2" | sort) | wc -l | xargs printf "\n相同内容文件数(按MD5): %d\n"
comm -23 <(awk '{print $1}' "$tmp1" | sort) <(awk '{print $1}' "$tmp2" | sort) | wc -l | xargs printf "仅存在于 %s 的内容块数: %d\n" "$dir1"
comm -13 <(awk '{print $1}' "$tmp1" | sort) <(awk '{print $1}' "$tmp2" | sort) | wc -l | xargs printf "仅存在于 %s 的内容块数: %d\n" "$dir2"
exit 0
fi
# ----- 严格按路径对比 -----
echo "--- 对比目录结构和文件内容 ---"
# 使用 awk 进行逐行比较,并收集统计信息
awk -v verbose="$verbose" -v d1="$dir1" -v d2="$dir2" '
function print_diff(path, md5a, md5b) {
if (md5a == md5b)
print "相同: " path
else
print "不同: " path " ( " d1 ": " md5a ", " d2 ": " md5b " )"
}
BEGIN { same=0; diff=0; only1=0; only2=0 }
NR==FNR {
# 读取第一个清单
path = $2; md5[path] = $1; next
}
{
# 读取第二个清单
path = $2;
if (path in md5) {
if (md5[path] == $1) {
same++;
} else {
diff++;
}
if (verbose) print_diff(path, md5[path], $1);
delete md5[path]; # 已匹配
} else {
only2++;
if (verbose) print "仅在 " d2 ": " path " (MD5: " $1 ")"
}
}
END {
# 剩余在 md5 中的即为仅在 dir1 的文件
for (path in md5) {
only1++;
if (verbose) print "仅在 " d1 ": " path " (MD5: " md5[path] ")"
}
# 始终输出统计摘要
printf "\n=== 统计摘要 ===\n"
printf "相同文件数(路径和内容均一致): %d\n", same
printf "内容不同的文件数(路径相同但MD5不同): %d\n", diff
printf "仅存在于 %s 的文件数: %d\n", d1, only1
printf "仅存在于 %s 的文件数: %d\n", d2, only2
}
' "$tmp1" "$tmp2"
exit 0
使用方法
-
保存脚本并赋予执行权限
bashsudo vi /usr/local/bin/compare_dirs_full.sh # 或任意位置 chmod +x /usr/local/bin/compare_dirs_full.sh -
运行(以您的目录为例)
-
仅显示统计摘要(不输出每个文件的详情) :
bashsudo compare_dirs_full.sh /root/var/www/html/upload /var/www/html/upload -
同时显示每个文件的详细状态 + 统计摘要 :
bashsudo compare_dirs_full.sh -v /root/var/www/html/upload /var/www/html/upload -
只比较文件内容(忽略路径) :
bashsudo compare_dirs_full.sh -c /root/var/www/html/upload /var/www/html/upload
-
输出示例
带 -v 时(部分输出):
正在生成目录 '/root/var/www/html/upload' 的 MD5 清单...
正在生成目录 '/var/www/html/upload' 的 MD5 清单...
--- 对比目录结构和文件内容 ---
相同: images/logo.png
不同: config.ini ( /root/var/www/html/upload: d41d8cd98f00b204e9800998ecf8427e , /var/www/html/upload: 098f6bcd4621d373cade4e832627b4f6 )
仅在 /var/www/html/upload: temp/cache.tmp (MD5: 5d41402abc4b2a76b9719d911017c592)
=== 统计摘要 ===
相同文件数(路径和内容均一致): 120
内容不同的文件数(路径相同但MD5不同): 3
仅存在于 /root/var/www/html/upload 的文件数: 5
仅存在于 /var/www/html/upload 的文件数: 2
不加 -v 时,只会输出摘要部分(不会有详细列表)。
关键改进点
- 始终输出统计摘要 :无论是否使用
-v,awk的END块都会打印四行统计。 - 详细模式与摘要并存 :
-v负责输出每行状态,摘要始终在最后给出。 - 清晰区分"路径相同但内容不同"和"仅存在某侧",便于定位问题。
- 临时文件自动清理,支持带空格的文件名。
如果文件数量极大(数十万级)
若目录包含海量文件,生成 MD5 列表可能较慢。此时可以考虑:
- 使用
parallel并行计算 MD5,或改用md5deep工具(可参考之前的建议)。 - 但本脚本对于常规规模(几万文件)完全够用。