查找重复的文件并将其替换为符号链接


16

我试图找到一种方法来检查给定目录中是否存在重复文件(甚至具有不同名称),并用指向首次出现的符号链接替换它们。我试过了,fdupes但只列出了那些重复项。
这就是上下文:我正在根据自己的喜好自定义图标主题,我发现很多图标,即使它们在父文件夹中具有不同的名称和位置,并且用于不同的用途,基本上也一样图片。由于在只需要一个修改的情况下进行二十或三十次相同的修改是多余的,因此我只想保留一个图像并符号链接所有其他图像。

例如,如果我fdupes -r ./在目录中运行testdir,它可能会返回以下结果:

./file1.png
./file2.png
./subdir1/anotherfile.png
./subdir1/subdir2/yetanotherfile.png

给定此输出,我想保留文件file1.png,删除所有其他文件,并用指向它的符号链接替换它们,同时保留所有原始文件名。因此file2.png将保留其名称,但将成为其链接file1.png而不是重复。

这些链接不应指向绝对路径,而应相对于父testdir目录。即yetanotherfile.png会点../../file1.png,不/home/testuser/.icons/testdir/file1.png

我对涉及GUI和CLI的解决方案都感兴趣。fdupes我引用了它不是强制性的,因为它是我所知道的工具,但是我也欢迎使用其他工具的解决方案。

我很确定创建一个处理所有这些内容的bash脚本应该不会那么困难,但是我不够熟练,无法找出如何自己编写它。

Answers:


3

第一; 您是否有理由需要使用符号链接而不是通常的硬链接?我很难理解使用相对路径进行符号链接的必要性。这是我如何解决此问题的方法:

我认为fdupes的Debian(Ubuntu)版本可以使用该-L选项用硬链接替换重复项,但是我没有安装Debian来验证这一点。

如果您没有带有该-L选项的版本,则可以使用在commandlinefu上找到的这个小型bash脚本。
请注意,此语法仅适用于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

上面的命令将在“路径”中找到所有重复的文件,并将其替换为硬链接。您可以通过运行ls -ilR并查看inode编号来验证这一点。这是一个包含十个相同文件的样本:

$ 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

所有文件都有单独的inode编号,从而使它们成为单独的文件。现在,让它们去重复:

$ 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

现在,文件都具有相同的索引节点号,这意味着它们都指向磁盘上的相同物理数据。

我希望这可以解决您的问题或至少为您指明正确的方向!


我记得fdupes可以选择用链接替换dupe,@ arnefm,但是我看不到男人的任何东西,也不是v1.51(Ubuntu 14.04.2 LTS)中的选项。
Alastair

jdupesgithub.com/jbruchon/jdupes上的fork 有一个-L选项,可以对重复的集合进行所需的硬链接。
乔迪·李·布鲁雄

我刚刚在这里调整了脚本。它仍然不会处理空格,但是会处理其他特殊字符(文件中有URL查询字符串)。另外,该${line//…/}部分对我不起作用,因此我采用了一种更干净的方法来将第一个“主”文件链接到硬链接。
IBBoard '18

1
如果要使用rsync其他类型的文件系统,是否需要相对的软链接?或者,如果文件系统没有保留层次结构,例如,它是将所有内容置于下面的备份服务器/«machine-name»/...?还是要从备份中还原?我看不到如何在这里保留硬链接。我可能会认为,相对的软链接会有更大的生存机会。
好友

6

如果您不喜欢太多脚本,那么我可以推荐rdfind。它将扫描给定目录中的重复文件,然后将它们硬链接或软链接在一起。我已经使用它对我的Ruby gems目录进行重复数据删除取得了巨大的成功。它在Debian / Ubuntu中可用。


4

我有类似的情况,但是在我的情况下,符号链接应该指向相对路径,所以我写了这个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)

对于每个输入行(这是文件列表),脚本都会拆分文件列表(以空格分隔),获取每个文件到第一个文件的相对路径,然后创建符号链接。


1

因此,arnefm提供的答案(已在整个Internet上复制)不处理文件名中的空格。我写了一个脚本来处理文件中的空格。

#!/bin/bash
fdupes -r -1 CHANGE_THIS_PATH | sed -e 's/\(\w\) /\1|/g' -e 's/|$//' > files
while read line; do
        IFS='|' read -a arr <<< "$line"
        orig=${arr[0]}
        for ((i = 1; i < ${#arr[@]}; i++)); do
                file="${arr[$i]}"
                ln -sf "$orig" "$file"
        done 
done < files

这样做的目的是找到重复对象,然后将它们插入PIPE并分成一个名为“ files”的文件。

然后,它将文件逐行读回数组,数组中的每个元素都由PIPE分隔。

然后,它遍历数组的所有非第一个元素,并用指向第一个元素的符号链接替换文件。

如果fdupes命令在子shell中执行,则可以删除外部文件(“文件”),而while可以直接读取,但是这种方式看起来更清晰。


2
此版本是否处理名称包含管道的文件?我假设这两个版本都不处理包含换行符的文件名,但这是fdupes的限制,而不是其他任何限制。
dhag 2015年

不会,但是您可以将IFS设置为您想要的任何值(也可以修改sed替换中的值),那么您应该没有任何问题(IFS设置为“ñ”或类似的名称应该可以使用)
David Ventura

这会创建损坏的符号链接,并且我有文件链接到它们自己。不要使用
MrMesees '10

0

预先警告:

  • 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

如果两个以上的文件重复(例如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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.