MD5校验对比脚本
- 始终显示统计摘要(相同、不同、仅存在于 dir1、仅存在于 dir2)
- 加上
-v选项时,同时逐行显示每个文件的详细对比状态 - 保留两种模式:严格对比(路径 + MD5)和仅内容对比(忽略路径)
- 自动处理权限,并给出友好的运行提示
完整脚本:compare_dirs_full.sh
#!/bin/bashset-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 EOFexit1}# 默认参数verbose=falsecontent_only=false# 解析选项OPTS=$(getopt-ovch--longverbose,content-only,help-n"$0"--"$@")if[$?!=0];thenusage;fievalset--"$OPTS"whiletrue;docase"$1"in-v|--verbose)verbose=true;shift;;-c|--content-only)content_only=true;shift;;-h|--help)usage;;--)shift;break;;*)break;;esacdone# 检查参数if[$#-ne2];thenecho"错误:需要两个目录作为参数"usagefidir1="$1"dir2="$2"# 验证目录存在if[!-d"$dir1"];thenecho"错误:目录 '$dir1' 不存在"exit1fiif[!-d"$dir2"];thenecho"错误:目录 '$dir2' 不存在"exit1fi# 创建临时文件并设置清理tmp1=$(mktemp)tmp2=$(mktemp)trap'rm -f "$tmp1" "$tmp2"'EXIT# 生成 MD5 清单:格式为 "MD5 相对路径"generate_list(){localdir="$1"localout="$2"(cd"$dir"&&find.-typef-execmd5sum{}+|sed's|^\./||'|sort-k2)>"$out"}echo"正在生成目录 '$dir1' 的 MD5 清单..."generate_list"$dir1""$tmp1"echo"正在生成目录 '$dir2' 的 MD5 清单..."generate_list"$dir2""$tmp2"# ----- 仅比较内容(忽略文件名) -----if["$content_only"=true];thenecho"--- 仅比较文件内容(忽略文件名) ---"# 提取 MD5 值排序后比较diff-u<(awk'{print $1}'"$tmp1"|sort)<(awk'{print $1}'"$tmp2"|sort)\|sed-e'/^---/d'-e'/^\+\+\+/d'-e'/^@@/d'||true# 统计:交集(相同MD5)、仅1、仅2comm-12<(awk'{print $1}'"$tmp1"|sort)<(awk'{print $1}'"$tmp2"|sort)|wc-l|xargsprintf"\n相同内容文件数(按MD5): %d\n"comm-23<(awk'{print $1}'"$tmp1"|sort)<(awk'{print $1}'"$tmp2"|sort)|wc-l|xargsprintf"仅存在于 %s 的内容块数: %d\n""$dir1"comm-13<(awk'{print $1}'"$tmp1"|sort)<(awk'{print $1}'"$tmp2"|sort)|wc-l|xargsprintf"仅存在于 %s 的内容块数: %d\n""$dir2"exit0fi# ----- 严格按路径对比 -----echo"--- 对比目录结构和文件内容 ---"# 使用 awk 进行逐行比较,并收集统计信息awk-vverbose="$verbose"-vd1="$dir1"-vd2="$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"exit0使用方法
保存脚本并赋予执行权限
sudovi/usr/local/bin/compare_dirs_full.sh# 或任意位置chmod+x /usr/local/bin/compare_dirs_full.sh运行(以您的目录为例)
- 仅显示统计摘要(不输出每个文件的详情):
sudocompare_dirs_full.sh /root/var/www/html/upload /var/www/html/upload - 同时显示每个文件的详细状态 + 统计摘要:
sudocompare_dirs_full.sh-v/root/var/www/html/upload /var/www/html/upload - 只比较文件内容(忽略路径):
sudocompare_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工具(可参考之前的建议)。 - 但本脚本对于常规规模(几万文件)完全够用。
