我有一个运行某个 Linux 版本的 NAS 盒,我用它来备份所有内容。
基本上可以肯定有些文件是完全相同的重复文件。
既然如此,我想做的是:
- 识别重复文件,其中“重复”= 相同的 SHA256 校验和。(相同的 SHA512 也可以接受,但可能需要更长时间。您建议哪种?)
- 允许其中一个副本作为“主”副本,删除所有其他副本,并将硬链接替换为剩余副本。这应该会在 NAS 卷上释放大量空间。
请注意,找到的第一个文件是“主”文件的不错选择,所有其他文件都可以删除并硬链接到它。权限和所有权不是问题,因为只有一个用户,而且(不要在这里讨厌我),无论如何,权限都是完全开放的。
还请注意,我想要硬链接,以便如果我删除一个文件(无论出于何种原因),所有其他文件都会保留。
请注意,我可以通过 SSH shell 控制台访问 NAS 盒。
问题:
- 是否可以 ?
- 我该怎么做 ?
- 如果某个文件有“X”个硬链接,而我删除了其他所有人都硬链接到的原始文件,那么剩余的硬链接是否仍保留到真实文件?(我怀疑答案是“是”,即一个文件会保留到所有硬链接都被删除为止。)
更新以添加其他上下文:
NAS 盒有两个驱动器,其中一个是外部的,可以移除,并且可以在我的 Ubuntu 笔记本电脑上处理。
另一个是内部的,基本上是不可触碰的。虽然我可以移除它,但它的设置方式非常定制,触碰它会迅速导致灾难。
此外,内部操作系统是运行 BusyBox 的 Linux“网络设备”版本。它似乎实现了标准功能,包括 find、grep、sed、awk 等。
即:(由 返回busybox --help
)
Currently defined functions:
adjtimex, ar, arp, arping, ash, awk, basename, cat, chgrp, chmod,
chown, chroot, clear, cmp, cp, crond, crontab, cut, date, dd, df,
dhcprelay, diff, dirname, dmesg, dnsdomainname, dos2unix, dpkg,
dumpleases, echo, egrep, env, expand, expr, false, fgrep, find, free,
fsck, getopt, getty, grep, halt, head, hostname, id, ifconfig,
ifenslave, init, ionice, ipcalc, kill, killall, ln, logger, logname,
logread, losetup, ls, lsof, lspci, lsusb, md5sum, mdev, mkdir, mkfifo,
mknod, mkswap, mktemp, modprobe, more, mount, mv, nice, nohup,
nslookup, pidof, ping, ping6, pivot_root, poweroff, printenv, printf,
ps, pwd, rdate, readlink, reboot, renice, rm, rmdir, route, sed, seq,
sh, sleep, sort, split, stat, swapoff, swapon, sync, sysctl, syslogd,
tac, tail, tar, tee, tftp, tftpd, top, touch, tr, traceroute,
traceroute6, true, tty, udhcpc, udhcpd, umount, uname, uniq, unix2dos,
uptime, usleep, vconfig, vi, watch, wc, which, xargs, zcip
答案1
1
是否可以?
是的,但不是,sha256sum
因为看起来你没有这个工具。你有md5sum
。
2
我该怎么做?
使用以下脚本。将其保存到文件(例如deduplicate
),使其可执行(chmod +x deduplicate
)。该脚本需要适当的输入和参数才能正确完成其工作,因此在运行它之前请阅读整个答案。
脚本
#/bin/sh
exec 3>/dev/tty
if echo | head -c 1 >/dev/null 2>/dev/null; then
fhead () { head -c 8192; }
else
fhead () { dd bs=1 count=8192 2>/dev/null; }
fi
grep -v '~DEDUPED~$' \
| (
while IFS= read -r pathname; do
wc -c -- "$pathname"
printf . >&3
done
) | sort -n \
| (
sequence=
oldsize=
oldpathname=
while IFS= read -r line; do
size="${line%% *}"
pathname="${line#* }"
if [ "$size" = "$oldsize" ]; then
if [ -z "$sequence" ]; then
sequence=y
printf '%s %s\n' "$(<"$oldpathname" fhead | md5sum -b)" "$oldpathname"
printf '+' >&3
fi
printf '%s %s\n' "$(<"$pathname" fhead | md5sum -b)" "$pathname"
printf '+' >&3
else
oldsize="$size"
oldpathname="$pathname"
sequence=
printf '-' >&3
fi
done
) | sort -k 1,1 \
| (
sequence=
oldsum=
oldpathname=
while IFS= read -r line; do
sum="${line%% *}"
pathname="${line#* ?- }"
if [ "$sum" = "$oldsum" ]; then
if [ -z "$sequence" ]; then
sequence=y
md5sum -b -- "$oldpathname"
printf '#' >&3
fi
md5sum -b -- "$pathname"
printf '#' >&3
else
oldsum="$sum"
oldpathname="$pathname"
sequence=
printf '=' >&3
fi
done
echo >&3
) | sort -k 1,1 \
| (
oldsum=
oldpathname=
while IFS= read -r line; do
sum="${line%% *}"
pathname="${line#* ?}"
if [ "$sum" = "$oldsum" ]; then
if [ "$1" = --no-dry-run ]; then
mv -- "$pathname" "$pathname~DEDUPED~" \
&& ln -f -- "$oldpathname" "$pathname" \
&& printf '%s ----> %s\n' "$pathname" "$oldpathname"
else
printf '%s ----> %s\n' "$pathname" "$oldpathname"
fi
else
oldsum="$sum"
oldpathname="$pathname"
fi
done )
用法
该脚本旨在从中读取路径名find
。注意事项:
- 因为硬链接仅在单个文件系统内有效,所以建议一次为一个文件系统(或其一部分)运行该脚本。
- 您应该只为脚本提供常规文件的路径名。
- 重复数据删除空文件不会给您太多空间(如果有的话),因此您可以将它们排除在外。
- 由于我不知道您的工具集是否支持使用以空字符结尾的字符串,因此所有内容都设计为使用以换行符结尾的字符串。您有责任不为脚本提供任何包含换行符的路径名。
所有这些意味着您应该运行如下命令:
find /the/mountpoint \
-xdev \
-type f \
-size +0 \
! -name "$(printf '*\n*')" \
| ./deduplicate
笔记像这样调用的脚本将仅打印它将执行的操作(并且形式可能不明确)。要使脚本真正影响文件系统(重复数据删除),您需要传递--no-dry-run
作为第一个参数(find … | ./deduplicate --no-dry-run
)。
程序
该脚本首先使用wc -c
。对于每个大小不唯一的文件,它计算md5sum
前 8192 个字节(head -c
如果支持,则使用回到dd
如果不是)。对于每个总和不唯一的文件,它会计算md5sum
整个文件的总和。结果相同的文件被视为重复文件:一堆中的第一个文件保持不变,其他文件将被替换为指向它的硬链接(如果--no-dry-run
)。请注意,“一堆中的第一个”不一定是“找到的第一个文件”。
这个脚本相当笨,它不检查它得到的一些路径名是否已经指向同一个文件(inode),它甚至不检查路径名是否相同。例如,如果你使用,find /the/mountpoint /the/mountpoint … | ./deduplicate
那么脚本将获取每个路径名两次,这将导致大量的工作徒劳无功。
可移植性
head -c
不可移植,但如果不受支持,则脚本将dd
自动恢复。
md5sum
不可移植,但我知道您的版本busybox
提供了它。我过去常常md5sum -b
明确请求二进制模式;这可能无关紧要。如果您md5sum
不理解,-b
那么您可以忽略此选项。
误报
最终总和相同的文件将被视为重复文件,最终总和是唯一标准;在此阶段,大小和部分总和甚至都不重要(这些只是为了尽早排除明显独特的文件)。脚本不会运行cmp
以确保ln
对真正相同的文件采取行动。
出于这个原因,我引入了mv -- "$pathname" "$pathname~DEDUPED~"
。此命令负责重命名否则将被硬链接替换的文件,从而“释放”路径名并备份旧内容。稍后,您可以通过调用
find /the/mountpoint -name '*~DEDUPED~'
检查其替换并根据需要恢复。请注意,脚本使用 过滤其输入grep -v '~DEDUPED~$'
,因此您可以再次运行它,它不会尝试对这些备份文件进行重复数据删除。
添加-exec rm {} + -print
如果您愿意,可以到上述命令的末尾批量删除所有备份文件。
所有权和模式
该脚本不关心所有权和模式(权限)。硬链接只是单个文件的不同路径名,仅此而已,因此如果ln
成功,则所有权和模式将与内容一样“共享”。所有权和/或权限可能会导致mv
或ln
失败。在评论中,您表示这对您来说不是问题。
但其他用户应注意。
进度指示器
脚本将进度指示器打印到/dev/tty
。在不同阶段,它由.
(“sizes” 阶段)-
和+
(“partial md5sum
” 阶段)或=
和#
(“whole md5sum
” 阶段)字符组成。这只是为了显示脚本正在进展。
该指标远非完美。其中可能会出现错误消息(如果有)。
摆脱指标的最简单方法是改为exec 3>/dev/tty
靠近exec 3>/dev/null
脚本的开头。
3
如果某个文件有“X”个硬链接,而我删除了其他所有人都硬链接到的原始文件,那么剩余的硬链接是否仍保留到真实文件?
是的。实际上,您不会删除“原始文件”,而只是取消链接名称。新的硬链接只是真实文件的替代路径名。旧路径名是(并且曾经是!)与新路径名一样硬链接。当它是链接到文件的唯一路径名时,我们通常不会将路径名称为“硬链接”;当有两个或多个路径名时,我们开始这样做,但每个路径名都相同,我们无法区分原始路径名。只有在取消链接所有名称后(并关闭所有句柄)文件系统将删除该文件。另请参阅此答案:硬链接的用例。