我试图找到一种方法来检查给定目录中是否有重复文件(即使名称不同),并将它们替换为指向第一次出现的符号链接。我已经尝试过,fdupes
但它只列出了那些重复项。
这就是上下文:我正在根据自己的喜好自定义图标主题,我发现许多图标,即使它们在父文件夹中具有不同的名称和不同的位置,并且用于不同的目的,但基本上都是相同的图片。由于当真正需要一次时应用相同的修改二十或三十次是多余的,因此我想只保留一张图像并符号链接所有其他图像。
例如,如果我fdupes -r ./
在目录中运行testdir
,它可能会返回以下结果:
./file1.png
./file2.png
./subdir1/anotherfile.png
./subdir1/subdir2/yetanotherfile.png
鉴于此输出,我想仅保留 file file1.png
,删除所有其他文件并用指向它的符号链接替换它们,同时保留所有原始文件名。因此file2.png
将保留其名称,但将成为链接file1.png
而不是重复。
这些链接不应指向绝对路径,而应相对于父testdir
目录;即将yetanotherfile.png
指向../../file1.png
,而不是指向/home/testuser/.icons/testdir/file1.png
我对涉及 GUI 和 CLI 的解决方案都感兴趣。我引用它并不是强制性的,fdupes
因为它是我所知道的工具,但我也愿意接受使用其他工具的解决方案。
我很确定创建一个处理所有这些问题的 bash 脚本应该不会那么困难,但我还不够专业,无法找到如何自己编写它。
答案1
如果您不喜欢太多脚本,那么我可以推荐查找。它将扫描给定目录中的重复文件,并用硬链接或符号链接替换它们。我已经使用它对 Ruby gems 目录进行重复数据删除,并取得了巨大成功。它可以在 Debian/Ubuntu 中使用。
答案2
我有类似的情况,但在我的情况下,符号链接应该指向相对路径,所以我写了这个 python 脚本做到这一点:
#!/usr/bin/env python
# Reads fdupes(-r -1) output and create relative symbolic links for each duplicate
# usage: fdupes -r1 . | ./lndupes.py
import os
from os.path import dirname, relpath, basename, join
import sys
lines = sys.stdin.readlines()
for line in lines:
files = line.strip().split(' ')
first = files[0]
print "First: %s "% first
for dup in files[1:]:
rel = os.path.relpath(dirname(first), dirname(dup))
print "Linking duplicate: %s to %s" % (dup, join(rel,basename(first)))
os.unlink(dup)
os.symlink(join(rel,basename(first)), dup)
对于每个输入行(文件列表),脚本会拆分文件列表(以空格分隔),获取从每个文件到第一个文件的相对路径,然后创建符号链接。
答案3
第一的;您需要使用符号链接而不是通常的硬链接是否有原因?我很难理解具有相对路径的符号链接的必要性。这是我解决这个问题的方法:
我认为 Debian (Ubuntu) 版本的 fdupes 可以使用该-L
选项用硬链接替换重复项,但我没有 Debian 安装来验证这一点。
如果您没有带有该-L
选项的版本,您可以使用我在上找到的这个小 bash 脚本命令行fu。
请注意,此语法仅适用于 bash。
fdupes -r -1 path | while read line; do master=""; for file in ${line[*]}; do if [ "x${master}" == "x" ]; then master=$file; else ln -f "${master}" "${file}"; fi; done; done
上面的命令将找到“path”中的所有重复文件,并将它们替换为硬链接。您可以通过运行ls -ilR
并查看索引节点号来验证这一点。这是包含十个相同文件的示例:
$ ls -ilR
total 20
3094308 -rw------- 1 username group 5 Sep 14 17:21 file
3094311 -rw------- 1 username group 5 Sep 14 17:21 file2
3094312 -rw------- 1 username group 5 Sep 14 17:21 file3
3094313 -rw------- 1 username group 5 Sep 14 17:21 file4
3094314 -rw------- 1 username group 5 Sep 14 17:21 file5
3094315 drwx------ 1 username group 48 Sep 14 17:22 subdirectory
./subdirectory:
total 20
3094316 -rw------- 1 username group 5 Sep 14 17:22 file
3094332 -rw------- 1 username group 5 Sep 14 17:22 file2
3094345 -rw------- 1 username group 5 Sep 14 17:22 file3
3094346 -rw------- 1 username group 5 Sep 14 17:22 file4
3094347 -rw------- 1 username group 5 Sep 14 17:22 file5
所有文件都有单独的索引节点号,使它们成为单独的文件。现在让我们对它们进行重复数据删除:
$ fdupes -r -1 . | while read line; do j="0"; for file in ${line[*]}; do if [ "$j" == "0" ]; then j="1"; else ln -f ${line// .*/} $file; fi; done; done
$ ls -ilR
.:
total 20
3094308 -rw------- 10 username group 5 Sep 14 17:21 file
3094308 -rw------- 10 username group 5 Sep 14 17:21 file2
3094308 -rw------- 10 username group 5 Sep 14 17:21 file3
3094308 -rw------- 10 username group 5 Sep 14 17:21 file4
3094308 -rw------- 10 username group 5 Sep 14 17:21 file5
3094315 drwx------ 1 username group 48 Sep 14 17:24 subdirectory
./subdirectory:
total 20
3094308 -rw------- 10 username group 5 Sep 14 17:21 file
3094308 -rw------- 10 username group 5 Sep 14 17:21 file2
3094308 -rw------- 10 username group 5 Sep 14 17:21 file3
3094308 -rw------- 10 username group 5 Sep 14 17:21 file4
3094308 -rw------- 10 username group 5 Sep 14 17:21 file5
现在,这些文件都具有相同的索引节点号,这意味着它们都指向磁盘上相同的物理数据。
我希望这能解决您的问题,或者至少为您指明正确的方向!
答案4
前面的一些注意事项:
- BASH 特定
- 文件名中没有空格
- 假设每行最多包含 2 个文件。
fdupes -1r common/base/dir | while read -r -a line ; do ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]}; done
如果超过 2 个文件重复(例如 file1 file2 file3),那么我们需要为每对文件创建一个符号链接 - 将 file1,file2 和 file1,file3 视为 2 个单独的情况:
if [[ ${#line[@]} -gt 2 ]] ;then
ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]}
ln -sf $(realpath --relative-to ${line[2]} ${line[0]}) ${line[2]}
...
fi
扩展它来自动处理每行任意数量的重复项将需要更多的努力。
另一种方法是首先创建绝对路径的符号链接,然后将它们转换:
fdupes -1r /absolute/path/common/base/dir | while read -r -a line ; do ln -sf ${line[0]} ${line[1]}; done
chroot /absolute/path/common/base/dir ; symlinks -cr .
这是基于@Gilles 的回答: https://unix.stackexchange.com/a/100955/77319