obsidian dataviewjs查找冗余文件

一、介绍

1.1 提出问题

起因我在整理网盘时,发现网盘的文件清理功能可以找出冗余的文件。然后扫描出我很多以前照片,pdf文档都是复制到了多个目录里。不过网盘上去删除这些重复的文件并没有本地的文件夹操作方便。

1.2 确定目标

于是我的目标就是:在本地用程序找出重复的文件。
因为obsidian已经成为了我的一个文件管理工具,而不仅仅只是markdown的笔记管理软件。我的一个更具体的目标就是能否在obsidian中实现冗余文件的查找。

二、调查方案

网上找了很多方案。对比如下:

方案1:专门的软件进行磁盘扫描。
优点:现成解决方案。
缺点:需要额外安装软件。不好集成到obsidian,因为我的文件管理是用obsidian所以还是希望找一个可以集成到obsidian。

方案2:用python程序自己实现。
优点:可以自己定制功能,自动化处理。
缺点:不好集成。

方案3:obsidian插件。
Local Images Plus插件,可以使用md5算法找出相同的照片。
awesome-image插件
unique-attachments:可以对照片一个hash值作为文件名。缺点:自动化命名文件导致文件名失去了可读性,为此我不得不又花了整天时间去恢复文件名,还是谨慎使用。
awesome-obsidian插件:不在插件市场里,需要自己手动安装。

目前遇到的插件的缺点:不能定制,不可控,照片名重命名后导致文档库需要重新整理。而且这些插件并不完全符合我清除冗余文件的目的。

方案4:obsidian用dataview的js语法实现。
优点:写程序逻辑,理论上可以实现很复杂的功能。
缺点:要编程技能,得益于AI时代可以帮助写代码。

三、实现方法

开发者文档: https://docs.obsidian.md/Home
经过调查所有的方案后我决定采用dataview的js程序实现。关于dataview的介绍我在另一个分享文档曾经介绍使用过,本文直接列举不同的解决方法。

3.1 方法1:找出所有重名md文件

效果:遍历dv.pages() 主要获取的是被视为页面(通常是 Markdown 文件)的数据。将文件名加入数组/hash表,遇到同名md文件就统计加1。最后展示数组/hash表中统计大于1的md文件。
js代码如下,这段代码来自obsidian论坛:

//dataviewjs
// 假设您的对象数组为 data
const data = dv.pages();let countMap = {}; // 用于存储计数的对象
let duplicates = []; // 用于存储重复元素的数组// 遍历对象数组
data.forEach((element) => {let fileName = element.file.name;let filePath = element.file.path;// 计数if (countMap[fileName]) {countMap[fileName].count++;countMap[fileName].paths.push(filePath);} else {countMap[fileName] = { count: 1, paths: [filePath] };}
});let dup=0//这是发现了几个dup的name
let flag =0//有重名文件的标志
for (const key in countMap) {const element = countMap[key];if (element.count > 1) {dup++;if(dup>0&&flag==0){dv.paragraph("==有重名文件==");flag=1}dv.span(`《${key}.md》出现了${element.count}次`);const pathstolink = element.paths.map(path => `[[${path}]]`);dv.list(pathstolink);}
}
if(dup==0){
dv.span("没有重名文件")
}

3.2 方法2:找出所有重名非md文件

3.1节方法存在只能找md文件,但往往大文件都是一些压缩包,pdf,照片文件。所以反而应该排除掉md文件,md文件很容易在自己写笔记的过程就能发现。要解决数据范围问题‌:对于一些非 Markdown 的文件,可能不在 dv.pages() 所涵盖的范围内。如果要获取所有文件(包括非 Markdown 文件),可以考虑使用 app.vault.getFiles() 等方式先获取所有文件,再进行过滤。

效果:过滤排除掉markdown文件的代码。

//dataviewjs
// 获取所有文件
const allFiles = app.vault.getFiles();
// 过滤出非md文件
const nonMdFiles = allFiles.filter(file => file.extension!== "md");
// 展示结果
dv.table(["文件名", "文件扩展名"],nonMdFiles.map(file => [file.name, file.extension])
);

3.2.1 列表展示同名非md文件

js代码如下

// 假设您的对象数组为 data
//const data = dv.pages();// 过滤非 Markdown 文件
const allFiles = app.vault.getFiles();
const nonMdFiles = allFiles.filter(file => !file.path.endsWith('.md'));let countMap = {}; // 用于存储计数的对象
let duplicates = []; // 用于存储重复元素的数组//const data = dv.pages().where(p => !p.file.path.endsWith(".md"));
//    .sort(p => p.file.name);// 遍历对象数组
for (let file of nonMdFiles){let fileName = file.name;let filePath = file.path;// 计数if (countMap[fileName]) {countMap[fileName].count++;countMap[fileName].paths.push(filePath);} else {countMap[fileName] = { count: 1, paths: [filePath] };}
}let dup=0//这是发现了几个dup的name
let flag =0//有重名文件的标志
const MAX_DUPLICATE_GROUPS = 2;for (const key in countMap) {if (dup >= MAX_DUPLICATE_GROUPS) {break;}const element = countMap[key];if (element.count > 1) {dup++;if(dup>0&&flag==0){dv.paragraph("==有重名文件==");flag=1}dv.span(`${key}出现了${element.count}次`);const pathstolink = element.paths.map(path => `[[${path}]]`);dv.list(pathstolink); //列表格式//dv.table(pathstolink);dv.table(['文件名', '文件类型', '文件大小', 'MD5值'],[key,element.paths,element.paths,element.paths,]);}
}
if(dup==0){
dv.span("没有重名文件")
}

3.2.2 表格展示文件名+文件大小

不但要比较文件名,还要比较大小。

AI提问:obsidian dataview优化这段代码,表格展示文件路径名和文件大小
用file name作为key记录hash表,然后统计出现次数
应该以md5作为key

js代码如下

//dataviewjs
// 过滤非 Markdown 文件
const allFiles = app.vault.getFiles();
const nonMdFiles = allFiles.filter(file =>!file.path.endsWith('.md'));// 用于存储计数的对象
const countMap = {};
// 用于存储重复元素的数组
const duplicates = [];// 遍历对象数组,统计文件出现次数和路径
for (let file of nonMdFiles) {const fileName = file.name;if (!countMap[fileName]) {countMap[fileName] = {count: 1,paths: [file.path],size: file.stat.size // 记录文件大小};} else {countMap[fileName].count++;countMap[fileName].paths.push(file.path);}
}let dupCount = 0; // 发现的重名文件数量
const MAX_DUPLICATE_GROUPS = 20;// 遍历统计结果,处理重名文件
for (const key in countMap) {if (dupCount >= MAX_DUPLICATE_GROUPS) {break;}const element = countMap[key];if (element.count > 1) {dupCount++;if (dupCount === 1) {dv.paragraph("==有重名文件==");}dv.span(`${key}出现了${element.count}次`);const tableData = element.paths.map(path => {const file = app.vault.getAbstractFileByPath(path);return [`[[${path}]]`, file.stat.size + ' bytes']; // 准备表格数据,包含路径和大小});dv.table(['文件路径', '文件大小'], tableData); // 展示表格}
}if (dupCount === 0) {dv.span("没有重名文件");
}

3.2.3 表格展示过滤出相等的文件名+文件大小

效果:查找性能高。原理:用文件名+文件大小作为key。
js代码如下

//dataviewjs
// 过滤非 Markdown 文件
const allFiles = app.vault.getFiles();
const nonMdFiles = allFiles.filter(file =>!file.path.endsWith('.md'));// 用于存储计数的对象
const countMap = {};
// 用于存储重复元素的数组
const duplicates = [];// 遍历对象数组,统计文件出现次数和路径
for (let file of nonMdFiles) {const fileName = file.name;const fileSize = file.stat.size;// 构建唯一键(文件名+文件大小,确保仅文件名和大小完全相同时匹配)const key = `${fileName}-${fileSize}`;if (!countMap[key]) {countMap[key] = {count: 1,paths: [file.path],size: file.stat.size // 记录文件大小};} else {countMap[key].count++;countMap[key].paths.push(file.path);}
}let dupCount = 0; // 发现的重名文件数量
const MAX_DUPLICATE_GROUPS = 200;// 遍历统计结果,处理重名文件
for (const key in countMap) {if (dupCount >= MAX_DUPLICATE_GROUPS) {break;}const element = countMap[key];if (element.count > 1) {dupCount++;if (dupCount === 1) {dv.paragraph("==有重名文件==");}dv.span(`${key}出现了${element.count}次`);const tableData = element.paths.map(path => {const file = app.vault.getAbstractFileByPath(path);return [`[[${path}]]`, file.stat.size + ' bytes']; // 准备表格数据,包含路径和大小});dv.table(['文件路径', '文件大小'], tableData); // 展示表格}
}if (dupCount === 0) {dv.span("扫描完成,没有重名文件!");
}

3.3 方法3:文件唯一性MD5值

文件的唯一确定其实是用文件的MD5值标识。只要两个文件的内容完全一样那MD5值也一样。文件名可以重命名成不同命,但MD5不会骗人。

3.3.1 表格展示所有文件的MD5值

AI提问: obsidian dataview展示每个文件(不仅仅是markdown文件)的md5值

// 统计所有类型文件
const files = app.vault.getAllLoadedFiles();

js代码如下

//dataviewjs
// 引入crypto模块用于计算MD5
const crypto = require('crypto');// 获取所有文件,并过滤掉Markdown文件
const files = app.vault.getFiles().filter(file => file.extension !== "md");// 创建表格数据
const tableData = await Promise.all(files.map(async (file) => {try {// 读取文件内容const content = await app.vault.read(file);// 计算MD5哈希值const hash = crypto.createHash('md5');hash.update(content);const md5Hash = hash.digest('hex');return {文件: file.link,文件名: file.name,文件夹: file.path,MD5值: md5Hash,文件大小: file.stat.size,修改时间: file.stat.mtime};} catch (error) {console.error(`计算文件 ${file.name} 的MD5时出错:`, error);return null;}
}));// 过滤掉计算失败的文件
const validData = tableData.filter(item => item !== null);// 生成表格
dv.table(["文件", "文件名", "MD5值", "文件大小", "修改时间"], validData.map(item => [item.文件,item.文件名,item.MD5值,`${(item.文件大小 / 1024).toFixed(2)} KB`,new Date(item.修改时间).toLocaleString()
]));

3.3.2 用MD5作为hash key

效果:通过使用MD5值作为hash表的key存储文件信息。我的仓库有5000个文件,使用后查找时间过长,我分析是因为计算MD5值消耗时间太多。实际时我只保留前几个查找结果。

js代码如下

//dataviewjs
// 过滤非 Markdown 文件
const allFiles = app.vault.getFiles();
const nonMdFiles = allFiles.filter(file =>!file.path.endsWith('.md'));// 引入CryptoJS库用于计算MD5
const crypto = require('crypto');// 用于存储计数的对象
const countMap = {};
// 用于存储重复元素的数组
const duplicates = [];// 遍历对象数组,统计文件出现次数和路径
for (let file of nonMdFiles) {const fileName = file.name;// 读取文件内容const fileContent = await app.vault.read(file);// 计算MD5值const hash = crypto.createHash('md5');hash.update(fileContent);const md5 = hash.digest('hex');if (!countMap[md5]) {countMap[md5] = {count: 1,paths: [file.path],size: file.stat.size // 记录文件大小};} else {countMap[md5].count++;countMap[md5].paths.push(file.path);}
}let dupCount = 0; // 发现的重名文件数量
const MAX_DUPLICATE_GROUPS = 2;// 遍历统计结果,处理重名文件
for (const key in countMap) {if (dupCount >= MAX_DUPLICATE_GROUPS) {break;}const element = countMap[key];if (element.count > 1) {dupCount++;if (dupCount === 1) {dv.paragraph("==有重名文件==");}dv.span(`${key}出现了${element.count}次`);const tableData = element.paths.map(path => {const file = app.vault.getAbstractFileByPath(path);return [path, file.stat.size + ' bytes']; // 准备表格数据,包含路径和大小});dv.table(['文件路径', '文件大小'], tableData); // 展示表格}
}if (dupCount === 0) {dv.span("没有重名文件");
}

3.3.3 表格显示前两组MD5相同文件

AI提问: 代码是dataview中统计同名文件并展示。请将这段代码改造为: obsidian dataview中展示每个文件(不仅仅是markdown文件)的md5值。需要过滤并展示出具有相同md5的文件,以列表格式展示,列表应该包含文件完整路径名,md5值。

js代码如下

//dataviewjs
// 引入CryptoJS库用于计算MD5
const crypto = require('crypto');// 获取所有非markdown文件
const files = app.vault.getFiles().filter(file => file.extension !== "md");// 存储MD5值和对应文件信息的对象
const md5Map = {};
const fileInfoMap = {};
let duplicateGroups = 0;
const MAX_DUPLICATE_GROUPS = 2;// 异步计算每个文件的MD5值
for (const file of files) {if (duplicateGroups >= MAX_DUPLICATE_GROUPS) {break;}try {// 读取文件内容const fileContent = await app.vault.read(file);// 计算MD5值const hash = crypto.createHash('md5');hash.update(fileContent);const md5 = hash.digest('hex');// 存储文件信息const fileInfo = {name: file.name,path: file.path,extension: file.extension,size: file.stat.size,mtime: file.stat.mtime};// 存储到映射中if (md5Map[md5]) {md5Map[md5].push(fileInfo);duplicateGroups++;} else {md5Map[md5] = [fileInfo];}fileInfoMap[file.path] = {md5: md5,...fileInfo};} catch (error) {console.log(`无法读取文件 ${file.path}: ${error}`);// 对于无法读取的文件,使用文件路径和大小计算MD5const fallbackContent = file.path + file.stat.size;//const fallbackMD5 = crypto.MD5(fallbackContent).toString();const fallbackhash = crypto.createHash('md5');fallbackhash.update(fallbackContent);const fallbackMD5 = hash.digest('hex');const fileInfo = {name: file.name,path: file.path,extension: file.extension,size: file.stat.size,mtime: file.stat.mtime,error: true};if (md5Map[fallbackMD5]) {md5Map[fallbackMD5].push(fileInfo);} else {md5Map[fallbackMD5] = [fileInfo];}fileInfoMap[file.path] = {md5: fallbackMD5,...fileInfo};}
}// 过滤出有重复MD5的文件组
const duplicates = {};
let hasDuplicates = false;for (const [md5, fileInfos] of Object.entries(md5Map)) {if (fileInfos.length > 1) {duplicates[md5] = fileInfos;hasDuplicates = true;}
}// 展示重复文件表格
if (hasDuplicates) {dv.paragraph("##  MD5重复文件检测结果");dv.paragraph(`共发现 **${Object.keys(duplicates).length}** 组重复文件`);// 为每个重复组创建表格let groupCount = 1;for (const [md5, fileInfos] of Object.entries(duplicates)) {dv.paragraph(`### 重复文件组 ${groupCount} (MD5: \`${md5.substring(0, 16)}...\`)`);// 创建表格头部const headers = ["文件名", "文件路径", "类型", "大小", "修改时间", "操作"];// 创建表格数据const tableData = fileInfos.map(info => [info.name,info.path,info.extension,`${(info.size / 1024).toFixed(2)} KB`,new Date(info.mtime).toLocaleString(),`[[${info.path}]]`]);// 渲染表格dv.table(headers, tableData);groupCount++;}
} else {dv.paragraph("## Y 未发现MD5重复文件");dv.paragraph("所有文件的MD5值都是唯一的。");
}// 显示统计信息
dv.paragraph("---");
dv.paragraph("##  文件MD5统计信息");const statsData = [["总文件数", files.length],["唯一MD5值数", Object.keys(md5Map).length],["重复文件组数", Object.keys(duplicates).length],["重复文件总数", Object.values(duplicates).reduce((sum, group) => sum + group.length, 0)]
];dv.table(["统计项", "数量"], statsData);// 可选:显示所有文件的MD5值(前20个作为示例)
dv.paragraph("---");
dv.paragraph("##  文件MD5值示例(前20个)");const sampleFiles = files.slice(0, 20);
const sampleTableData = sampleFiles.map(file => {const info = fileInfoMap[file.path];return [file.name,file.extension,`${(file.stat.size / 1024).toFixed(2)} KB`,info ? `\`${info.md5.substring(0, 16)}...\`` : "计算失败",info?.error ? "N" : "Y"];
});dv.table(["文件名", "类型", "大小", "MD5(前16位)", "状态"], sampleTableData);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/990637.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模板索引 字符串

字典树点击查看代码 void insert(char *str){int rot = 0; // 当前子树根节点int len = strlen(str);int res = 0 ; // 表示字典树路径上存在有几个别的单词,用于这道题统计答案for(int i = 0; i < len; i++){int …

2025.12.6日22:51-patriarchal家长的;族长的;由族长统治的

ai智能发布助手当前Windows 11已使用内存:MB是102 南京4℃ 霾 Appreciate the beauty and goodness around you; it enriches life. 欣赏周围美好的事物和人,让生活更加丰富。 今日热点如下 挑战者杯,我的哪吒与变形…

[模板] 字符串

KMP点击查看代码 vector<int> prefix_function(string s) {int n = (int)s.length();vector<int> pi(n);for (int i = 1; i < n; i++) {int j = pi[i - 1];while (j > 0 && s[i] != s[j]) j…

树基础

树基础#define 柚子树 右子树0x00 树基本 0x01 树 树是啥?一种抽象但相当有用的数据结构。 树的结构特殊性使得它易于计算信息,并用来出各种各样的题。其各种变体还可以用于维护信息。 我们听说过的线段树、树状数组…

2025最新深圳餐饮食材配送服务商/厂家TOP5推荐!全品类供应+一体化服务权威榜单发布,赋能餐饮企业降本增效新生态

随着餐饮行业竞争日益激烈,高效、优质的食材配送服务成为餐饮企业提升竞争力的关键。本榜单基于产品丰富度、配送效率、定制化服务能力、研发赋能四大维度,结合行业调研数据与客户反馈,权威解析2025年深圳地区五大餐…

数据采集与融合技术作业4

作业4 要求: ▪ 熟练掌握 Selenium 查找 HTML 元素、爬取 Ajax 网页数据、等待 HTML 元素等内 容。 ▪ 使用 Selenium 框架+ MySQL 数据库存储技术路线爬取“沪深 A 股”、“上证 A 股”、 “深证 A 股”3 个板块的股…

数字马力二面准备-后端开发郑州岗(校招)

首先就是自我介绍 面试官您好! 我叫xxx,21岁,石家庄铁道大学软件工程专业2026届本科生,应聘贵公司 Java 后端开发岗位。 在校期间,我系统学习了数据结构、面向对象编程、软件工程等核心课程,并扎实掌握了 Java、…

2024 MUCAR BT200 PRO OBD2 Scanner: Full System Diagnostic 15 Resets Wireless Code Reader

The MUCAR BT200 PRO OBD2 Scanner: Redefining Car Diagnostics for European & American Mechanics and Owners Why Struggling with Car Diagnostics Isn’t Your Fault For European and American mechanics, …

Last Dance

NOIP2025 最后一站CSP-2025 退役老人限时返场。 哦这个 T3 怎么这么难写。这个 T4 又一点都不会。算了,4h 的比赛还是太短了。 后来发现我已经推到 T4 容斥前的一步了,容斥后 DP 不难,可惜我根本没有想过要用容斥(…

12.6笔记

实验四:SMO 算法实现与测试 一、实验目的 深入理解支持向量机(SVM)的算法原理,掌握使用 Python 语言实现支持向量机的训练与测试方法,熟练运用五折交叉验证算法进行模型性能评估,并理解SMO算法在SVM训练中的作用…

【亲测免费】 开源项目html2image常见问题解决方案 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

AT_agc002_d 题解

AT_agc002_d 题解题意 有一张 \(n\) 个点,\(m\) 条边的无向图,点的编号从 \(1\) 到 \(n\),边的编号从 \(1\) 到 \(m\),第 \(i\) 条边连接定点 \(a_i\) 和 \(b_i\)。保证图联通。 在这张图上,有 \(Q\) 次询问,每次…

2025最新东莞简餐快餐菜品研发培训服务商/厂家TOP5评测!全链条赋能+实战落地权威榜单发布,助力餐饮品牌破解同质化难题

随着简餐快餐行业竞争日趋激烈,菜品创新与标准化成为门店突围的核心关键。本榜单基于服务覆盖广度、研发实战能力、供应链协同效率三大维度(零厨供应链新增"全品类赋能"维度),结合行业客户反馈与服务案例…

完整教程:新手做网站如何被百度快速收录教程

完整教程:新手做网站如何被百度快速收录教程2025-12-06 22:30 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: b…

smartbits是啥

smartbits是啥在工控、通信、测试、电子行业里,你提到的 SmartBits 通常指的是 一种网络/通信测试设备。下面我详细解释:✅ SmartBits 是什么? SmartBits 是 Ixia 公司(原 IBM/Agilent 出品)的一系列网络测试仪器…

vxe-gantt 甘特图实现产品进度列表,自定义任务条样式和提示信息

vxe-gantt 甘特图实现产品进度列表,自定义任务条样式和提示信息 查看官网:https://gantt.vxeui.com/ gitbub:https://github.com/x-extends/vxe-gantt gitee:https://gitee.com/x-extends/vxe-gantt 效果代码 通过…

12月6日总结 - 作业----

12月6日总结学习六级内容

11.6

后端核心框架/技术SpringBoot:Java全栈主流后端框架,简化Spring配置,快速搭建微服务或单体应用(如开发接口、集成数据库)。 ​ SpringMVC:Spring生态的MVC架构实现,负责处理HTTP请求(接收前端参数、返回JSON数…

触摸未来2025-11-09:万有力,图论革命 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …