因为我的群晖型号是DS218+没有照片去重功能,加上自己工作电脑太多,经常重复备份很多资料。看过网上很多删除重复文件方法,试过,比较麻烦而且删不干净!我估计自己电脑重复文件,最少3W个以上。

思路:


找出重复文件  --> 提取重复文件路径  -->  用python程序读出路径并且删除文件


具体步骤:

1、存储空间分析器

使用存储空间分析器,新建任务;

立即生成报告并且查看报告

下载报告并且提取路径

excel当中使用IF条件,筛选出重复

使用筛选功能提取重复

保存为1.txt 此文件保存为重复文件的路径;我保存在home目录下面,

对应为/volume1/homes/xudada/1.txt(第一部分结束了)

2、配置NAS环境

在开源里面找到python并且安装

在社群里面找到terminal并且安装

启动terminal的时候需要权限。(注意会弹出窗口,记住上面的命令

 sudo sed -i 's/package/root/g' /var/packages/terminal/conf/privilege

使用secureCRT远程连接NAS,设置权限;

3、python代码部分

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
群晖重复文件删除工具
安全删除文本文件中列出的文件,并显示详细的删除动作
"""

import os
import sys
import time
import shutil
import argparse
from pathlib import Path
from typing import List, Tuple

class FileDeleter:
    """文件删除器类"""
    
    def __init__(self, list_file: str, simulate: bool = False, verbose: bool = False):
        """
        初始化文件删除器
        
        Args:
            list_file: 包含文件路径的文本文件
            simulate: 模拟模式,只显示不实际删除
            verbose: 详细模式,显示更多信息
        """
        self.list_file = Path(list_file).resolve()
        self.simulate = simulate
        self.verbose = verbose
        self.deleted_count = 0
        self.skipped_count = 0
        self.error_count = 0
        self.total_size = 0
        
        # 颜色代码(在终端中显示颜色)
        self.COLORS = {
            'RED': '\033[91m',
            'GREEN': '\033[92m',
            'YELLOW': '\033[93m',
            'BLUE': '\033[94m',
            'MAGENTA': '\033[95m',
            'CYAN': '\033[96m',
            'RESET': '\033[0m',
            'BOLD': '\033[1m',
        }
    
    def color_text(self, text: str, color: str) -> str:
        """为文本添加颜色"""
        if sys.stdout.isatty():
            return f"{self.COLORS.get(color, '')}{text}{self.COLORS['RESET']}"
        return text
    
    def print_header(self):
        """打印程序头信息"""
        print(self.color_text("=" * 70, "BLUE"))
        print(self.color_text("群晖重复文件删除工具", "BOLD") + self.color_text(" v1.0", "YELLOW"))
        print(self.color_text("=" * 70, "BLUE"))
        print(f"文件列表: {self.color_text(str(self.list_file), 'CYAN')}")
        print(f"模式: {self.color_text('模拟运行' if self.simulate else '实际删除', 'YELLOW')}")
        print(f"开始时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
        print(self.color_text("-" * 70, "BLUE"))
    
    def print_footer(self):
        """打印程序尾信息"""
        print(self.color_text("-" * 70, "BLUE"))
        print(f"完成时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
        print(self.color_text("=" * 70, "BLUE"))
        
        # 统计信息
        total_files = self.deleted_count + self.skipped_count + self.error_count
        
        print(f"\n{self.color_text('删除统计:', 'BOLD')}")
        print(f"总文件数: {total_files}")
        print(f"已删除: {self.color_text(str(self.deleted_count), 'GREEN')}")
        print(f"已跳过: {self.color_text(str(self.skipped_count), 'YELLOW')}")
        print(f"错误: {self.color_text(str(self.error_count), 'RED')}")
        
        if self.total_size > 0:
            size_str = self.format_size(self.total_size)
            print(f"释放空间: {self.color_text(size_str, 'GREEN')}")
        
        print(self.color_text("=" * 70, "BLUE"))
    
    def format_size(self, size_bytes: int) -> str:
        """格式化文件大小"""
        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
            if size_bytes < 1024.0:
                return f"{size_bytes:.2f} {unit}"
            size_bytes /= 1024.0
        return f"{size_bytes:.2f} PB"
    
    def clean_file_list(self) -> List[str]:
        """
        清理和验证文件列表
        
        Returns:
            清理后的有效文件路径列表
        """
        if not self.list_file.exists():
            print(self.color_text(f"错误: 文件列表 '{self.list_file}' 不存在!", "RED"))
            sys.exit(1)
        
        print(f"正在读取文件列表: {self.color_text(str(self.list_file), 'CYAN')}")
        
        cleaned_paths = []
        with open(self.list_file, 'r', encoding='utf-8', errors='ignore') as f:
            for line_num, line in enumerate(f, 1):
                # 清理行:去除首尾空白字符,包括换行符
                path_str = line.strip()
                
                # 跳过空行和注释行
                if not path_str or path_str.startswith('#'):
                    continue
                
                # 处理相对路径:转换为绝对路径
                path = Path(path_str)
                if not path.is_absolute():
                    # 相对于文件列表所在目录
                    path = self.list_file.parent / path
                
                # 解析符号链接
                try:
                    if path.is_symlink():
                        real_path = path.resolve()
                        if self.verbose:
                            print(f"行{line_num}: 符号链接 {path} -> {real_path}")
                        path = real_path
                except:
                    pass  # 无法解析符号链接,保持原路径
                
                cleaned_paths.append(str(path))
        
        print(f"找到 {len(cleaned_paths)} 个文件路径")
        return cleaned_paths
    
    def delete_file(self, file_path: str) -> Tuple[bool, str, int]:
        """
        删除单个文件
        
        Args:
            file_path: 要删除的文件路径
            
        Returns:
            (成功标志, 消息, 文件大小)
        """
        path = Path(file_path)
        
        # 检查文件是否存在
        if not path.exists():
            return False, "文件不存在", 0
        
        # 检查是否是文件(不是目录)
        if not path.is_file():
            return False, "不是常规文件(可能是目录)", 0
        
        # 获取文件大小
        try:
            file_size = path.stat().st_size
        except:
            file_size = 0
        
        # 检查权限
        if not os.access(str(path), os.W_OK):
            return False, "没有写入权限", file_size
        
        # 执行删除
        try:
            if self.simulate:
                return True, "模拟删除成功", file_size
            else:
                path.unlink()
                return True, "删除成功", file_size
        except Exception as e:
            return False, f"删除失败: {str(e)}", file_size
    
    def process_files(self, file_paths: List[str]):
        """处理所有文件"""
        print(f"\n{self.color_text('开始处理文件:', 'BOLD')}")
        
        for i, file_path in enumerate(file_paths, 1):
            # 显示处理进度
            progress = f"[{i}/{len(file_paths)}]"
            
            # 删除文件
            success, message, file_size = self.delete_file(file_path)
            
            # 更新统计
            if success:
                self.deleted_count += 1
                self.total_size += file_size
                status_color = "GREEN"
                status_mark = "✓"
            else:
                if "不存在" in message or "不是常规文件" in message:
                    self.skipped_count += 1
                    status_color = "YELLOW"
                    status_mark = "○"
                else:
                    self.error_count += 1
                    status_color = "RED"
                    status_mark = "✗"
            
            # 显示结果
            file_display = file_path
            if len(file_display) > 60:
                file_display = "..." + file_display[-57:]
            
            status = self.color_text(status_mark, status_color)
            size_info = f" ({self.format_size(file_size)})" if file_size > 0 else ""
            
            print(f"{progress} {status} {file_display}{size_info}")
            
            if self.verbose and message:
                print(f"     {self.color_text(message, status_color)}")
    
    def run(self):
        """运行删除程序"""
        self.print_header()
        
        # 清理和获取文件列表
        file_paths = self.clean_file_list()
        
        if not file_paths:
            print(self.color_text("没有找到有效的文件路径!", "YELLOW"))
            self.print_footer()
            return
        
        # 确认操作(非模拟模式时)
        if not self.simulate:
            print(f"\n{self.color_text('警告:', 'RED')} 即将删除 {len(file_paths)} 个文件!")
            confirm = input(f"{self.color_text('是否继续? (y/N): ', 'YELLOW')}")
            if confirm.lower() != 'y':
                print(self.color_text("操作已取消", "YELLOW"))
                return
        
        # 处理文件
        self.process_files(file_paths)
        
        # 显示统计信息
        self.print_footer()

def main():
    """主函数"""
    parser = argparse.ArgumentParser(
        description="群晖重复文件删除工具 - 安全删除文本文件中列出的文件",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
使用示例:
  %(prog)s filelist.txt               # 实际删除文件
  %(prog)s filelist.txt --simulate    # 模拟运行,不实际删除
  %(prog)s filelist.txt --verbose     # 详细输出模式
  
文件列表格式:
  每行一个文件路径,支持绝对路径或相对路径
  空行和以#开头的行会被忽略
        """
    )
    
    parser.add_argument(
        "list_file",
        help="包含要删除文件路径的文本文件"
    )
    
    parser.add_argument(
        "-s", "--simulate",
        action="store_true",
        help="模拟模式,显示将要删除的文件但不实际执行"
    )
    
    parser.add_argument(
        "-v", "--verbose",
        action="store_true",
        help="详细输出模式"
    )
    
    parser.add_argument(
        "-q", "--quiet",
        action="store_true",
        help="安静模式,只显示错误信息"
    )
    
    args = parser.parse_args()
    
    # 设置输出级别
    if args.quiet:
        sys.stdout = open(os.devnull, 'w')
    
    try:
        # 创建删除器并运行
        deleter = FileDeleter(
            list_file=args.list_file,
            simulate=args.simulate,
            verbose=args.verbose
        )
        deleter.run()
    except KeyboardInterrupt:
        print(f"\n{deleter.color_text('操作被用户中断', 'YELLOW')}")
        sys.exit(1)
    except Exception as e:
        print(f"\n{deleter.color_text(f'程序错误: {str(e)}', 'RED')}")
        sys.exit(1)

if __name__ == "__main__":
    main()

将python代码保存为file_deleter.py(路径依然为home)

在nas中使用文本编辑器保存,命名为file_deleter.py

在terminal当中为file_deleter.py赋予权限

python3 file_deleter.py /volume1/homes/xudada/1.txt

输入Y进行下一步

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐