用 fdupes 将重复文件替换为硬链接并核算空间收益

举报
yd_217513249 发表于 2026/10/29 13:58:44 2026/10/29
【摘要】 介绍在 Linux 上用 fdupes 递归找出重复文件,先按大小缩小候选,再比对内容并把多余副本替换为硬链接;脚本校验 inode 与链接数,并按旧 inode 的分配块数统计实际节省空间。

用 fdupes 找出重复文件并把多余副本替换为硬链接

场景

在数据盘、构建缓存或归档目录中,常见内容完全相同但路径不同的文件。直接删除多余副本有风险,保留又多占空间。若这些文件位于同一文件系统,且后续不需要独立修改,可以把多余副本替换为指向保留文件的硬链接:目录结构不变,重复数据只保留一份。

本文用 fdupes 找出重复组,用脚本逐个替换硬链接,并校验 inode 与链接数。空间统计按旧副本 inode 实际分配的 512 字节块计算,而不是简单累加文件逻辑大小。

思路

fdupes -r DIR 会递归扫描目录。它先按文件大小缩小候选范围,再对同大小文件比对内容,因此适合做重复文件发现。脚本读取 fdupes 输出的重复组,每组保留第一个文件,其余文件用 ln -f 指向保留文件。链接前用 cmp -s 再做一次内容复核,并检查设备号,避免跨文件系统硬链接失败。链接后检查两个路径的 inode 是否一致、链接数是否至少为 2。

注意:硬链接共享 inode,权限、属主、时间戳等元数据也会跟随保留文件;如果原副本权限不同,替换后会丢失差异。只建议用于归档、只读缓存或可接受统一元数据的目录。

最小脚本

#!/usr/bin/env bash
set -euo pipefail

root=${1:?用法: $0 DIR}
list=$(mktemp)
trap 'rm -f "$list"' EXIT

# 只列出重复组,不自动删除;fdupes 内部按大小预筛选并比对内容
fdupes -r -- "$root" > "$list"

saved=0
while IFS=$'\t' read -r keep dup; do
  [[ -f "$keep" && -f "$dup" ]] || continue
  [[ "$keep" != "$dup" ]] || continue

  keep_dev=$(stat -c %d -- "$keep")
  dup_dev=$(stat -c %d -- "$dup")
  if [[ "$keep_dev" != "$dup_dev" ]]; then
    printf '跳过跨文件系统: %s -> %s\n' "$dup" "$keep"
    continue
  fi

  keep_ino=$(stat -c %i -- "$keep")
  dup_ino=$(stat -c %i -- "$dup")
  [[ "$keep_ino" == "$dup_ino" ]] && continue

  if ! cmp -s -- "$keep" "$dup"; then
    printf '内容不一致,跳过: %s -> %s\n' "$dup" "$keep"
    continue
  fi

  old_links=$(stat -c %h -- "$dup")
  old_blocks=$(stat -c %b -- "$dup")   # 512 字节块
  ln -f -- "$keep" "$dup"

  new_keep_ino=$(stat -c %i -- "$keep")
  new_dup_ino=$(stat -c %i -- "$dup")
  new_links=$(stat -c %h -- "$dup")

  if [[ "$new_keep_ino" != "$new_dup_ino" || "$new_links" -lt 2 ]]; then
    printf '校验失败: %s -> %s\n' "$dup" "$keep" >&2
    exit 1
  fi

  if [[ "$old_links" -eq 1 ]]; then
    saved=$((saved + old_blocks * 512))
  fi
  printf '已链接: %s -> %s (inode=%s, links=%s)\n' \
    "$dup" "$keep" "$new_dup_ino" "$new_links"
done < <(awk 'BEGIN{RS="";FS="\n"} NF>1 {for(i=2;i<=NF;i++) print $1 "\t" $i}' "$list")

printf '按 512 字节块估算,本次实际释放: %d 字节\n' "$saved"

运行方法

  1. 先在测试目录准备若干重复文件,并保留备份。
  2. 保存脚本为 dedupe-hardlink.sh,执行 chmod +x dedupe-hardlink.sh。
  3. 用占位目录运行:./dedupe-hardlink.sh /data/placeholder。
  4. 查看输出中的 inode、links 和释放字节数;再用 stat 抽查:
stat -c '%i %h %s %n' /data/placeholder/a.bin /data/placeholder/b.bin

两个路径应显示相同 inode,链接数至少为 2。

边界与限制

  • 硬链接只能在同一文件系统内创建;脚本会跳过设备号不同的组合。
  • 只处理普通文件;目录、符号链接、特殊文件不应按此方式替换。
  • 文件名含换行符时,基于按行解析的示例不适用;生产环境应使用 NUL 分隔并确认 fdupes 版本支持。
  • 旧副本 inode 若还有其它硬链接,替换当前路径不会立即释放数据块,脚本只在旧链接数为 1 时计入节省。
  • 运行前先备份;替换会改变元数据关联,且后续对任一链接的写入会影响所有链接。
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。