我在一个目录下的树中保存了很多音乐,为了保证质量,以我最初获取的任何格式保存。我有第二个目录树,其结构相似,但所有文件都采用有损压缩格式,可通过我的手机播放,并且偶尔会更改元数据(例如,删除嵌入的封面以节省空间)。
我发现对于音乐的很大一部分,这两个实例之间没有区别 - 通常当分发版本仅以 mp3/ogg 形式提供并且没有嵌入封面时。硬盘空间可能很便宜,但这没有理由浪费它。有没有办法编写脚本:
- 检查两个目录中是否有相同的文件
- 每当找到相同的文件时,将一个文件替换为另一个文件的硬链接
- 例如,为了节省时间,无需花时间获得完整的差异
- 但仍然没有意外删除两个不同文件的副本的风险,如果我只是比较哈希值,这是一个遥远但非零的机会?
答案1
以下命令用于md5
为当前目录或以下目录中的所有文件生成 MD5 摘要:
find . -type f -exec md5 {} +
如果您没有 BSD实用程序,请替换md5
为。md5sum --tag
md5
让我们构建一个简单的脚本来在目录上执行此操作:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
这需要命令行上的两个目录并生成名为md5.1
和的文件md5.2
,每个目录对应一个文件,位于/tmp
(或$TMPDIR
指向的任何位置)。这些文件按照 MD5 摘要排序。
这些文件看起来像
MD5 (<path>) = <MD5 digest>
每个文件都有这样一行。
然后,在同一脚本中,比较两个文件之间的校验和:
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12]
这使用校验和作为连接字段在两个文件之间执行关系“连接”操作。两个字段中具有相同校验和的任何行都将被合并并输出。
如果两个文件中的任何校验和相同,则会输出:
<space><MD5 digest>=MD5 (<path1>) =MD5 (<path2>)
这可以直接传递给awk
解析出两个路径:
awk -F '[()]' 'BEGIN { OFS="\t" } { print $2, $4 }'
这-F [()]
只是一种表达方式,我们希望将每一行划分为基于(
和 的字段)
。这样做会给我们留下字段 2 和 4 中的路径。
这将输出
<path1><tab><path2>
然后只需读取这些制表符分隔的路径对并发出正确的命令来创建链接即可:
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
总之:
#!/bin/bash
tmpdir=${TMPDIR:-/tmp}
if (( $# != 2 )); then
echo 'Expected two directories as arguments' >&2
exit 1
fi
i=0
for dir in "$@"; do
(( ++i ))
find "$dir" -type f -exec md5 {} + | sort -t '=' -k2 -o "$tmpdir/md5.$i"
done
join -t '=' -1 2 -2 2 "$tmpdir"/md5.[12] |
awk -F '\\)|\\(' 'BEGIN { OFS="\t" } { print $2, $4 }' |
while IFS=$'\t' read -r path1 path2; do
echo ln -f "$path1" "$path2"
done
rm -f "$tmpdir"/md5.[12]
在循环echo
中while
是为了安全。运行一次看看会发生什么,如果您确信它正在做正确的事情,请将其删除并再次运行它。
请记住,硬链接不能跨越分区。这意味着两个目录需要位于同一分区上。文件位于第二如果发现重复目录将被覆盖。将原件的备份保存在某处,直到您对结果感到满意为止!
请注意,如果任何文件的文件名中包含(
或或 制表符,则此解决方案将无法正常工作。)
答案2
除非您有大量非常相似的文件,否则计算和比较哈希值不会加快查找重复项的过程。最慢的操作是磁盘读取。计算哈希值意味着读取整个文件,而且它是一项 CPU 密集型任务,具有现代加密强哈希值。
仅当文件长度不同时我们才必须比较数据。如果只有一个给定长度的文件,显然不存在重复项。如果有两个,简单地比较它们总是比散列更有效。如果有三个或更多,比较次数会增加,但很可能它们的第一个字节或块不同,因此磁盘 I/O 仍然较低,重复读取会从缓存返回。
这就是为什么我建议制作一个递归目录列表,准备一个长度+路径名列表,然后按数字对列表进行排序,最后通过成对比较来仅处理共享相同长度的文件集。如果两个文件匹配,则可以用硬链接替换其中一个。