44 个版本 (24 个破坏性更新)
0.34.0 | 2023年10月29日 |
---|---|
0.32.2 | 2023年10月18日 |
0.32.1 | 2023年6月11日 |
0.30.0 | 2023年3月12日 |
0.12.3 | 2021年6月20日 |
#204 在 文件系统
每月下载量292次
用于 fclones-gui
460KB
10K SLoC
fclones
高效的重复文件查找和删除工具
这是命令行 fclones 及其核心库的仓库。关于桌面前端,请参阅 fclones-gui。
fclones
是一个命令行工具,用于识别相同文件组并删除不再需要的文件副本。它提供了丰富的配置选项来控制搜索范围,并提供多种删除重复文件的方法。为了最大的灵活性,它很好地集成了其他 Unix 工具,如 find
,并且支持 JSON,因此您可以对搜索和清理过程有更多的控制。
fclones
对您的数据非常认真。在删除之前,您可以检查和修改重复文件的列表。还有一个 --dry-run
选项可以告诉您文件系统上将要进行哪些更改。
fclones
使用 Rust 实现,在现代化硬件上具有高性能。它采用了许多其他程序中不存在的一些优化技术。它适应硬盘类型,按照 HDD 上的物理数据放置顺序执行文件操作,并行扫描目录树,并在处理数百万文件时使用路径的前缀压缩以减少内存消耗。它还友好地处理页面缓存,不会将数据推离缓存。因此,fclones
在 SSD 或 HDD 存储上比许多其他流行的重复文件查找器表现得更好。
fclones
可用于多种操作系统,但在 Linux 上表现最佳。
功能
- 识别相同文件组
- 查找重复文件
- 查找超过 N 个副本的文件
- 查找唯一文件
- 查找少于 N 个副本的文件
- 高级文件选择,以减少处理的数据量
- 扫描多个目录根
- 可以与从标准输入直接管道传输的文件列表一起工作
- 递归/非递归文件选择
- 递归深度限制
- 通过扩展UNIX globs过滤名称和路径
- 通过正则表达式过滤名称和路径
- 根据最小/最大文件大小过滤
- 正确处理符号链接和硬链接
- 移除冗余数据
- 移除、移动或用软或硬链接替换文件
- 使用某些文件系统上的本地copy-on-write(reflink)支持移除冗余的文件数据
- 根据路径或名称模式选择要删除的文件
- 根据创建时间、修改时间、最后访问时间或嵌套级别优先删除文件
- 高性能
- 在所有I/O和CPU密集型阶段具有并行处理能力
- 根据设备类型(SSD vs HDD)自动调整并行性和访问策略
- 由于高度优化的路径表示,内存占用低
- 提供多种快速非加密和加密哈希函数,宽达512位
- 不会将数据推离页面缓存(仅限Linux)
- 可选的文件哈希持久缓存
- 精确的进度报告
- 多种输出格式,便于进一步处理结果
- 标准文本格式
- 使用分组标题分隔的组,包含文件大小和哈希
- 每组一行路径
- 可选的
fdupes
兼容性(无标题,无缩进,组之间由空白行分隔) - 机器可读格式:
CSV
,JSON
- 标准文本格式
限制
Windows不支持copy-on-write文件数据去重(reflink)。
某些优化在其他平台(除Linux外)不可用
- 根据物理位置对文件访问进行排序
- 页面缓存滞后
演示
让我们首先创建一些文件
$ mkdir test
$ cd test
$ echo foo >foo1.txt
$ echo foo >foo2.txt
$ echo foo >foo3.txt
$ echo bar >bar1.txt
$ echo bar >bar2.txt
现在让我们识别重复项
$ fclones group . >dupes.txt
[2021-06-05 18:21:33.358] fclones: info: Started grouping
[2021-06-05 18:21:33.738] fclones: info: Scanned 7 file entries
[2021-06-05 18:21:33.738] fclones: info: Found 5 (20 B) files matching selection criteria
[2021-06-05 18:21:33.738] fclones: info: Found 4 (16 B) candidates after grouping by size
[2021-06-05 18:21:33.738] fclones: info: Found 4 (16 B) candidates after grouping by paths and file identifiers
[2021-06-05 18:21:33.739] fclones: info: Found 3 (12 B) candidates after grouping by prefix
[2021-06-05 18:21:33.740] fclones: info: Found 3 (12 B) candidates after grouping by suffix
[2021-06-05 18:21:33.741] fclones: info: Found 3 (12 B) redundant files
$ cat dupes.txt
# Report by fclones 0.12.0
# Timestamp: 2021-06-05 18:21:33.741 +0200
# Command: fclones group .
# Found 2 file groups
# 12 B (12 B) in 3 redundant files can be removed
7d6ebf613bf94dfd976d169ff6ae02c3, 4 B (4 B) * 2:
/tmp/test/bar1.txt
/tmp/test/bar2.txt
6109f093b3fd5eb1060989c990d1226f, 4 B (4 B) * 3:
/tmp/test/foo1.txt
/tmp/test/foo2.txt
/tmp/test/foo3.txt
最后,我们可以用软链接替换重复项
$ fclones link --soft <dupes.txt
[2021-06-05 18:25:42.488] fclones: info: Started deduplicating
[2021-06-05 18:25:42.493] fclones: info: Processed 3 files and reclaimed 12 B space
$ ls -l
total 12
-rw-rw-r-- 1 pkolaczk pkolaczk 4 cze 5 18:19 bar1.txt
lrwxrwxrwx 1 pkolaczk pkolaczk 18 cze 5 18:25 bar2.txt -> /tmp/test/bar1.txt
-rw-rw-r-- 1 pkolaczk pkolaczk 382 cze 5 18:21 dupes.txt
-rw-rw-r-- 1 pkolaczk pkolaczk 4 cze 5 18:19 foo1.txt
lrwxrwxrwx 1 pkolaczk pkolaczk 18 cze 5 18:25 foo2.txt -> /tmp/test/foo1.txt
lrwxrwxrwx 1 pkolaczk pkolaczk 18 cze 5 18:25 foo3.txt -> /tmp/test/foo1.txt
安装
代码已在Ubuntu Linux 21.10上彻底测试。Windows或Mac OS X等其他系统可能也能运行。欢迎测试和/或将代码移植到其他平台。请报告成功和失败的情况。
官方包
Snap商店(Linux)
snap install fclones
Homebrew(macOS和Linux)
brew install fclones
某些平台的安装包和二进制文件直接附加到发布。
第三方包
从源码构建
安装Rust工具链然后运行
cargo install fclones
构建将二进制文件写入.cargo/bin/fclones
。
使用方法
fclones
提供查找和删除文件的不同命令。这样,您可以在对文件系统进行任何修改之前检查找到的文件列表。
group
- 识别相同的文件组并将它们打印到标准输出remove
- 删除先前由group
识别的冗余文件link
- 用链接替换冗余文件(默认:硬链接)dedupe
- 不删除任何文件,但通过使用文件系统的本地copy-on-write功能(reflink)来去重文件数据
查找文件
在当前目录及其子目录中查找重复、唯一、复制不足或复制过多的文件
fclones group .
fclones group . --unique
fclones group . --rf-under 3
fclones group . --rf-over 3
您可以在多个目录中进行搜索
fclones group dir1 dir2 dir3
默认情况下,隐藏文件和与.gitignore
和.fdignore
中列出的模式匹配的文件将被忽略。要搜索所有文件,请使用
fclones group --no-ignore --hidden dir
限制递归深度
fclones group . --depth 1 # scan only files in the current dir, skip subdirs
fclones group * --depth 0 # similar as above in shells that expand `*`
注意:0.10版本以下的版本默认不会进入目录。在这些旧版本中,添加-R
标志以启用递归目录遍历。
找到匹配两个目录树中的文件,而不匹配每个树中的相同文件
fclones group --isolate dir1 dir2
找到至少100 MB大小的重复文件
fclones group . -s 100M
按文件名或路径模式过滤
fclones group . --name '*.jpg' '*.png'
在find
选定的文件上运行fclones
(注意:这可能比内置过滤慢)
find . -name '*.c' | fclones group --stdin --depth 0
跟随符号链接,但不逃离主目录
fclones group . -L --path '/home/**'
从扫描中排除目录树的一部分
fclones group / --exclude '/dev/**' '/proc/**'
删除文件
要删除重复文件,需要将它们移动到不同的位置或用链接替换,你需要将fclones group
生成的报告发送到fclones remove
、fclones move
或fclones link
命令的标准输入。报告格式将自动检测。目前支持default
和json
报告格式。
假设重复文件列表已保存在文件dupes.txt
中,以下命令将删除冗余文件
fclones link <dupes.txt # replace with hard links
fclones link -s <dupes.txt # replace with symbolic links
fclones move target_dir <dupes.txt # move to target_dir
fclones remove <dupes.txt # remove totally
如果您希望一次完成所有操作而不将组列表存储在文件中,可以使用管道
fclones group . | fclones link
要选择要保留的文件数量,请使用-n
/--rf-over
选项。默认情况下,它设置为运行group
时使用的值(如果没有明确设置,则为1)。要为每个组留下2个副本,请运行
fclones remove -n 2 <dupes.txt
默认情况下,fclones
遵循输入文件中指定的文件顺序。它保留每个列表开头的文件,并删除/替换每个列表末尾的文件。可以通过--priority
选项更改此顺序,例如
fclones remove --priority newest <dupes.txt # remove the newest replicas
fclones remove --priority oldest <dupes.txt # remove the oldest replicas
有关更多优先级选项,请参阅fclones remove --help
。
还可以将删除文件的限制仅限于与模式匹配的文件名或路径
fclones remove --name '*.jpg' <dupes.txt # remove only jpg files
fclones remove --path '/trash/**' <dupes.txt # remove only files in the /trash folder
如果指定不想删除的文件的模式更容易,则可以使用keep
选项之一
fclones remove --keep-name '*.mov' <dupes.txt # never remove mov files
fclones remove --keep-path '/important/**' <dupes.txt # never remove files in the /important folder
为了确保您不会意外删除错误的文件,请使用--dry-run
选项。此选项将打印出将要执行的所有命令,但实际上不会执行它们
fclones link --soft <dupes.txt --dry-run 2>/dev/null
mv /tmp/test/bar2.txt /tmp/test/bar2.txt.jkXswbsDxhqItPeOfCXsWN4d
ln -s /tmp/test/bar1.txt /tmp/test/bar2.txt
rm /tmp/test/bar2.txt.jkXswbsDxhqItPeOfCXsWN4d
mv /tmp/test/foo2.txt /tmp/test/foo2.txt.ze1hvhNjfre618TkRGUxJNzx
ln -s /tmp/test/foo1.txt /tmp/test/foo2.txt
rm /tmp/test/foo2.txt.ze1hvhNjfre618TkRGUxJNzx
mv /tmp/test/foo3.txt /tmp/test/foo3.txt.ttLAWO6YckczL1LXEsHfcEau
ln -s /tmp/test/foo1.txt /tmp/test/foo3.txt
rm /tmp/test/foo3.txt.ttLAWO6YckczL1LXEsHfcEau
处理链接
由符号链接或硬链接连接的文件不视为重复文件。您可以通过设置以下标志来更改此行为
- 当设置
--isolate
时- 位于不同目录树中的链接被视为重复项
- 位于同一目录树中的链接计为一个副本。
- 当设置
--match-links
时,fclones将所有链接文件视为重复文件。
考虑以下目录结构,其中所有文件都是共享相同内容的硬链接
dir1:
- file1
- file2
dir2:
- file3
- file4
因为所有文件本质上是相同的数据,它们最终将出现在同一个文件组中,但该文件组中实际存在的副本数量将根据提供的标志而有所不同
命令 | 副本数量 | 报告的组 | 要删除的文件 |
---|---|---|---|
fclones group dir1 dir2 |
1 | 无 | |
fclones group dir1 dir2--isolate |
2 | 是 | file3, file4 |
fclones group dir1 dir2--match-links |
4 | 是 | file2, file3, file4 |
符号链接
group
命令默认忽略文件符号链接,除非设置了至少 --follow-links
或 --symbolic-links
标志。如果仅设置了 --follow-links
,则会跟随文件符号链接并解析到它们的目标。如果设置了 --symbolic-links
,则不会跟随文件符号链接,但会将其视为硬链接并在输出报告中可能报告。当同时设置了 --symbolic-links
和 --follow-links
时,会跟随目录符号链接,但文件符号链接会被视为硬链接。
注意:将 --match-links
与 --symbolic-links
一起使用非常危险。很容易导致删除唯一的常规文件,并留下大量孤儿符号链接。
预处理器文件
使用 --transform
选项通过外部命令安全地转换文件。默认情况下,转换发生在文件数据的副本上,以避免意外数据丢失。请注意,此选项可能会显著减慢大量文件的处理速度,因为它为每个文件调用外部程序。
以下命令在匹配重复的 jpg 图像之前会去除 exif 信息
fclones group . --name '*.jpg' -i --transform 'exiv2 -d a $IN' --in-place
其他
列出更多选项
fclones [command] -h # short help
fclones [command] --help # detailed help
路径通配符
fclones
理解 Bash 扩展通配符的一个子集。可以使用以下通配符:
?
匹配除目录分隔符以外的任何字符[a-z]
匹配方括号中给出的字符或字符范围之一[!a-z]
匹配方括号中未给出的任何字符*
匹配除目录分隔符以外的任何字符序列**
匹配包括目录分隔符在内的任何字符序列{a,b}
匹配花括号内逗号分隔的任意一个模式@(a|b)
与{a,b}
相同?(a|b)
匹配方括号内模式的最多一次出现+(a|b)
匹配方括号内给定模式的至少一次出现*(a|b)
匹配方括号内给定模式的任意次数出现\
在 Unix-like 系统上转义通配符,例如\?
会匹配?
的字面意思^
在 Windows 上转义通配符,例如^?
会匹配?
的字面意思
注意
-
在类Unix系统中,使用通配符时必须非常小心,以避免shell意外地扩展通配符。在许多情况下,您可能不希望shell而不是
fclones
来扩展通配符。在这种情况下,您需要引用通配符。fclones group . --name '*.jpg'
-
在Windows上,默认的shell在将参数传递给程序之前不会移除引号,因此您需要不引用地传递通配符。
fclones group . --name *.jpg
-
在Windows上,默认的shell不支持路径通配符,因此路径中使用的通配符*和?将被原样传递,并且很可能创建无效的路径。例如,以下在Bash中在当前目录中搜索重复文件的命令,在默认的Windows shell中可能会失败:
fclones group *
如果您需要路径通配符,而您的shell不支持它,请使用
--name
或--path
提供的内置路径通配符。
算法
文件在几个阶段进行处理。除了最后一个阶段之外,每个阶段都是并行的,但必须完全完成上一个阶段才能开始下一个阶段。
- 扫描输入文件并过滤与选择标准匹配的文件。如果请求,递归地遍历目录。如果请求,则跟随符号链接。对于与选择标准匹配的文件,读取它们的尺寸。
- 通过将它们存储在哈希映射中按尺寸分组收集的文件。删除小于所需下限(默认为2)的小组。
- 在每组中,删除具有相同inode id的重复文件。当存在硬链接时,相同文件可能通过不同的路径访问。此步骤可以可选地跳过。
- 对于每个剩余的文件,计算初始数据小块的哈希。将具有不同哈希的文件放入不同的组中。如有必要,修剪结果组。
- 对于每个剩余的文件,计算文件末尾数据小块的哈希。将具有不同哈希的文件放入不同的组中。如有必要,修剪小组。
- 对于每个剩余的文件,计算文件全部内容的哈希。请注意,对于小文件,我们可能在步骤4中已经计算了完整的文件内容哈希,因此可以安全地忽略这些文件。与步骤4和5相同,分割组并移除那些过小的组。
- 将报告写入stdout。
请注意,没有进行逐字节比较文件。所有可用的哈希函数至少为128位宽,您不需要担心哈希冲突。在1015个文件中,使用128位哈希时,冲突的概率为0.000000001,不考虑文件还需要匹配尺寸的要求。
哈希
您可以使用--hash-fn
(默认:metro
)选择哈希函数。非加密哈希比加密哈希效率高得多,但是除非您从快速的SSD读取或文件数据已缓存,否则您可能不会看到太大的差异。
哈希函数 | 哈希宽度 | 加密 |
---|---|---|
metro | 128位 | 无 |
xxhash3 | 128位 | 无 |
blake3 | 256位 | 是 |
sha256 | 256位 | 是 |
sha512 | 512位 | 是 |
sha3-256 | 256位 | 是 |
sha3-512 | 512位 | 是 |
调整
本节提供了关于如何从fclones
中获得最佳性能的提示。
增量模式
如果您预计将在同一组文件上多次运行fclones group
,您可能从启用哈希缓存中受益,通过添加--cache
标志。
fclones group --cache <dir>
缓存可以在后续运行fclones
时显著提高分组速度,但需要额外的存储空间来存储缓存。缓存还允许在中断后快速恢复工作,因此如果您计划在大型数据集上运行fclones
,则建议使用它。
缓存的工作方式如下:
- 每个新计算的文件哈希都会持久化在缓存中,同时还有一些文件元数据,例如修改时间戳和长度。
- 每当需要计算文件哈希时,首先会在缓存中查找。如果当前文件的元数据与缓存中存储的元数据严格匹配,则会使用缓存的哈希值。
由于文件是通过其内部标识符(如Unix上的inode标识符)来识别的,而不是通过路径名,且移动/重命名通常保留这些标识符,因此缓存的哈希值不会被文件移动操作失效。
请注意,缓存依赖于文件元数据来检测文件内容的更改。如果文件在修改时没有立即更新文件修改时间戳和文件长度,这可能会在分组过程中引入一些不准确之处。大多数文件系统在关闭文件时自动更新时间戳。因此,长时间保持打开状态(例如,由数据库系统保持打开)的已更改文件可能不会被 fclones group
发现,并可能使用过时的缓存值。
缓存数据库位于用户账户的标准缓存目录中。通常,这些是
- Linux:
$HOME/.cache/fclones
- macOS:
$HOME/Library/Caches/fclones
- Windows:
$HOME/AppData/Local/fclones
配置并行性
--threads
参数控制内部线程池的大小。当您不希望 fclones
过度影响系统性能时,可以使用此参数来降低并行级别,例如,当您需要同时执行其他工作时。如果您需要减少内存使用,我们建议降低并行级别。
当使用 fclones
的版本 0.6.x 或更高版本,对每个文件至少有几种MB大小的文件进行去重时
在旋转磁盘中(HDD),建议将 --threads 1
设置,因为在HDD上从多个线程访问大文件可能比单线程访问慢得多(YMMV,这高度依赖于操作系统,已报道的性能差异为2x-10x)。
从版本 0.7.0 开始,fclones 使用针对最终哈希的独立设备线程池,并根据设备类型自动调整并行级别、内存缓冲区大小和部分哈希大小。这些自动设置可以通过 -threads
覆盖。
以下选项可以传递给 --threads
。更具体的选项会覆盖不具体的选项。
main:<n>
– 设置用于随机I/O(目录树扫描、文件元数据检索和内存排序/哈希)的主线程池的大小。这些操作通常从高并行级别中受益,即使在旋转磁盘上也是如此。默认情况下未设置,这意味着池将配置为使用所有可用的CPU核心。dev:<device>:<r>,<s>
– 设置在具有给定名称的块设备上用于随机I/O的线程池r
和用于顺序I/O的线程池s
的大小。设备名称依赖于操作系统。请注意,这不同于分区名称或挂载点。ssd:<r>,<s>
– 设置用于固态硬盘I/O的线程池大小。默认不设置。hdd:<r>,<s>
– 设置用于旋转硬盘I/O的线程池大小。默认为8,1
removable:<r>,<s>
– 设置用于可移动设备(例如USB闪存盘)I/O的线程池大小。默认为4,1
unknown:<r>,<s>
– 设置用于未知类型设备I/O的线程池大小。有时设备类型无法确定,例如如果它被挂载为NAS。默认为4,1
default:<r>,<s>
– 设置所有未设置选项使用的池大小<r>,<s>
- 与default:<r>,<s>
<n>
- 与default:<n>,<n>
示例
将主线程池的并行级别限制为1
fclones group <paths> --threads main:1
限制所有SSD设备的所有I/O访问的并行级别
fclones group <paths> --threads ssd:1
将随机I/O访问的并行级别设置为核心数,将顺序I/O访问的并行级别设置为2,针对/dev/sda
块设备
fclones group <paths> --threads dev:/dev/sda:0,2
可以给出多个 --threads
选项,用空格分隔
fclones group <paths> --threads main:16 ssd:4 hdd:1,1
基准测试
不同的重复查找器被分配了在大量文件集中查找重复的任务。在每次运行之前,使用 echo 3 > /proc/sys/vm/drop_caches
清除系统页面缓存。
SSD基准测试
- 型号:Dell Precision 5520
- CPU:Intel(R) Xeon(R) CPU E3-1505M v6 @ 3.00GHz
- RAM:32 GB
- 存储:本地NVMe SSD 512 GB
- 系统:Ubuntu Linux 20.10,内核5.8.0-53-generic
- 任务:1,460,720路径,316 GB数据
程序 | 版本 | 语言 | 时间 | 峰值内存 |
---|---|---|---|---|
fclones | 0.12.1 | Rust | 0:34.59 | 266 MB |
yadf | 0.15.2 | Rust | 0:59.32 | 329 MB |
czkawka | 3.1.0 | Rust | 2:09.00 | 1.4 GB |
rm lint | 2.9.0 | C, Python | 2:28.43 | 942 MB |
jdupes | 1.18.2 | C | 5:01.91 | 332 MB |
dupe-krill | 1.4.5 | Rust | 5:09.52 | 706 MB |
fdupes | 2.1.1 | C | 5:46.19 | 342 MB |
rdfind | 1.4.1 | C++ | 5:53.07 | 496 MB |
dupeguru | 4.1.1 | Python | 7:49.89 | 1.4 GB |
fdupes-java | 1.3.1 | Java | >> 20 minutes | 4.2 GB |
fdupes-java
测试未完成。在它仍在第二/第三阶段计算 MD5 的 20 分钟后,我中断了它。不幸的是 fdupes-java
不显示有用的进度条,因此无法估计它需要多长时间。
HDD 基准测试
- 型号:Dell Precision M4600
- CPU:Intel(R) Core(TM) i7-2760QM CPU @ 2.40GHz
- RAM:24 GB
- 系统:Mint Linux 19.3,内核 5.4.0-70-generic
- 存储:Seagate Momentus 7200 RPM SATA 硬盘,EXT4 文件系统
- 任务:51370 个路径,2 GB 数据,6811 个(471 MB)重复文件
使用的命令
/usr/bin/time -v fclones -R <file set root>
/usr/bin/time -v jdupes -R -Q <file set root>
/usr/bin/time -v fdupes -R <file set root>
/usr/bin/time -v rdfind <file set root>
在这个基准测试中,在每次运行之前都删除了页面缓存。
程序 | 版本 | 语言 | 线程 | 时间 | 峰值内存 |
---|---|---|---|---|---|
fclones | 0.9.1 | Rust | 1 | 0:19.45 | 18.1 MB |
rdfind | 1.3.5 | C++ | 1 | 0:33.70 | 18.5 MB |
yadf | 0.14.1 | Rust | 1:11.69 | 22.9 MB | |
jdupes | 1.9 | C | 1 | 1:18.47 | 15.7 MB |
fdupes | 1.6.1 | C | 1 | 1:33.71 | 15.9 MB |
依赖项
~20–33MB
~507K SLoC