自动匹配文件名相似文件脚本

wen 实用脚本 2

本文目录导读:

自动匹配文件名相似文件脚本

  1. Python版本 (推荐)
  2. Bash版本 (简单直接)
  3. 高级Python版本 (带GUI和更多功能)
  4. 使用方法
  5. 功能特点

我来帮你创建一个自动匹配文件名相似文件的脚本,这里提供Python和Bash两个版本:

Python版本 (推荐)

#!/usr/bin/env python3
"""
文件名相似度匹配脚本
功能:查找并匹配相似文件名的文件
"""
import os
import sys
import re
import argparse
from collections import defaultdict
from difflib import SequenceMatcher
def get_file_list(directory, extensions=None, recursive=False):
    """获取文件列表"""
    files = []
    if recursive:
        for root, dirs, filenames in os.walk(directory):
            for filename in filenames:
                filepath = os.path.join(root, filename)
                if extensions:
                    if any(filename.endswith(ext) for ext in extensions):
                        files.append(filepath)
                else:
                    files.append(filepath)
    else:
        for filename in os.listdir(directory):
            filepath = os.path.join(directory, filename)
            if os.path.isfile(filepath):
                if extensions:
                    if any(filename.endswith(ext) for ext in extensions):
                        files.append(filepath)
                else:
                    files.append(filepath)
    return files
def clean_filename(filename):
    """清理文件名,提取纯名称(不含扩展名)"""
    name = os.path.splitext(os.path.basename(filename))[0]
    # 移除常见编号和标点
    name = re.sub(r'[\d\-_\(\)\[\]]+', ' ', name)
    name = re.sub(r'\s+', ' ', name).strip()
    return name.lower()
def calculate_similarity(name1, name2):
    """计算两个文件名的相似度"""
    return SequenceMatcher(None, name1, name2).ratio()
def find_similar_files(files, threshold=0.6, min_group=2):
    """查找相似文件并分组"""
    groups = []
    processed = set()
    for i, file1 in enumerate(files):
        if file1 in processed:
            continue
        name1 = clean_filename(file1)
        group = [file1]
        for j, file2 in enumerate(files[i+1:], i+1):
            if file2 in processed:
                continue
            name2 = clean_filename(file2)
            similarity = calculate_similarity(name1, name2)
            if similarity >= threshold:
                group.append(file2)
                processed.add(file2)
        if len(group) >= min_group:
            groups.append(group)
            processed.add(file1)
    return groups
def match_by_pattern(files, pattern_type='prefix'):
    """按文件名模式匹配"""
    groups = defaultdict(list)
    for filepath in files:
        filename = os.path.basename(filepath)
        name = os.path.splitext(filename)[0]
        if pattern_type == 'prefix':
            # 按前缀匹配(前N个字符)
            key = name[:3] if len(name) >= 3 else name
        elif pattern_type == 'suffix':
            # 按后缀匹配(后N个字符)
            key = name[-3:] if len(name) >= 3 else name
        elif pattern_type == 'number':
            # 按数字部分匹配
            numbers = re.findall(r'\d+', name)
            key = numbers[0] if numbers else name
        else:
            key = name
        groups[key].append(filepath)
    return [group for group in groups.values() if len(group) >= 2]
def main():
    parser = argparse.ArgumentParser(description='文件名相似度匹配工具')
    parser.add_argument('directory', help='要扫描的目录')
    parser.add_argument('-t', '--threshold', type=float, default=0.6,
                      help='相似度阈值 (0-1),默认0.6')
    parser.add_argument('-r', '--recursive', action='store_true',
                      help='递归扫描子目录')
    parser.add_argument('-e', '--extensions', nargs='+',
                      help='筛选文件扩展名,如 .jpg .png')
    parser.add_argument('-m', '--mode', choices=['similarity', 'pattern'],
                      default='similarity', help='匹配模式')
    parser.add_argument('-p', '--pattern', choices=['prefix', 'suffix', 'number'],
                      default='prefix', help='模式匹配类型')
    parser.add_argument('--min-group', type=int, default=2,
                      help='最小分组大小,默认2')
    args = parser.parse_args()
    # 验证目录
    if not os.path.isdir(args.directory):
        print(f"错误:目录 '{args.directory}' 不存在")
        sys.exit(1)
    # 获取文件列表
    print(f"扫描目录: {args.directory}")
    files = get_file_list(args.directory, args.extensions, args.recursive)
    print(f"找到 {len(files)} 个文件")
    if not files:
        print("没有找到匹配的文件")
        return
    # 查找相似文件
    if args.mode == 'similarity':
        print(f"使用相似度匹配模式 (阈值: {args.threshold})")
        groups = find_similar_files(files, args.threshold, args.min_group)
    else:
        print(f"使用模式匹配模式 (模式: {args.pattern})")
        groups = match_by_pattern(files, args.pattern)
    # 输出结果
    print(f"\n找到 {len(groups)} 组相似文件:\n")
    for idx, group in enumerate(groups, 1):
        print(f"组 {idx} ({len(group)} 个文件):")
        for filepath in sorted(group):
            print(f"  - {filepath}")
        print()
    # 生成操作建议
    if groups:
        print("=" * 50)
        print("操作建议:")
        print("=" * 50)
        print("1. 查看以上分组,确认是否为重复或相似文件")
        print("2. 如需删除冗余文件,请先手动确认")
        print("3. 可使用以下命令移动文件:")
        print('   mkdir -p similar_files && mv pattern similar_files/')
if __name__ == "__main__":
    main()

Bash版本 (简单直接)

#!/bin/bash
"""
文件名相似度匹配脚本 (Bash版本)
简单易用,适合快速检查
"""
# 设置颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}文件名相似匹配工具${NC}"
echo "----------------------------------------"
# 检查参数
if [ $# -lt 1 ]; then
    echo "用法: $0 <目录> [阈值] [递归]"
    echo "示例: $0 ./myfiles 0.6 true"
    exit 1
fi
DIRECTORY="$1"
THRESHOLD="${2:-0.6}"
RECURSIVE="${3:-false}"
if [ ! -d "$DIRECTORY" ]; then
    echo -e "${RED}错误: 目录不存在${NC}"
    exit 1
fi
echo -e "${YELLOW}扫描目录: $DIRECTORY${NC}"
# 收集所有文件
if [ "$RECURSIVE" = "true" ]; then
    FILES=$(find "$DIRECTORY" -type f)
else
    FILES=$(find "$DIRECTORY" -maxdepth 1 -type f)
fi
TOTAL_FILES=$(echo "$FILES" | wc -l)
echo -e "${YELLOW}找到 $TOTAL_FILES 个文件${NC}"
echo "----------------------------------------"
# 按文件名前缀分组(前5个字符)
echo "按文件名前缀分组(前5个字符):"
echo "$FILES" | while read file; do
    basename "$file"
done | sort | awk '
{
    prefix = substr($0, 1, 5)
    files[prefix] = files[prefix] ? files[prefix] "\n" $0 : $0
    count[prefix]++
}
END {
    for (prefix in files) {
        if (count[prefix] >= 2) {
            printf "\n%s%s (%d files):%s\n", "\033[1;33m", prefix, count[prefix], "\033[0m"
            split(files[prefix], arr, "\n")
            for (i in arr) {
                print "  - " arr[i]
            }
        }
    }
}
'
# 查找文件名包含数字的相似文件
echo -e "\n${GREEN}包含数字的文件名:${NC}"
echo "$FILES" | while read file; do
    basename "$file"
done | grep -E '[0-9]+' | sort -u
# 简单的相似度检查(基于文件名长度差异)
echo -e "\n${GREEN}文件名长度相似的文件(长度差<=2):${NC}"
declare -A name_lengths
while IFS= read -r file; do
    name=$(basename "$file")
    len=${#name}
    name_lengths["$name"]=$len
done <<< "$FILES"
# 输出长度相近的文件
for name1 in "${!name_lengths[@]}"; do
    len1=${name_lengths[$name1]}
    for name2 in "${!name_lengths[@]}"; do
        if [ "$name1" != "$name2" ]; then
            len2=${name_lengths[$name2]}
            diff=$(( len1 - len2 ))
            if [ ${diff#-} -le 2 ]; then
                echo "  $name1 <-> $name2 (长度差: $diff)"
            fi
        fi
    done
done | sort -u
echo -e "\n${GREEN}完成!${NC}"

高级Python版本 (带GUI和更多功能)

#!/usr/bin/env python3
"""
高级文件名匹配工具
支持模糊匹配、正则匹配、批量操作
"""
import os
import sys
import re
import json
import shutil
from pathlib import Path
from typing import List, Tuple, Dict
from difflib import SequenceMatcher
from collections import defaultdict
import hashlib
import datetime
class FileNameMatcher:
    """文件名匹配器"""
    def __init__(self, base_dir: str):
        self.base_dir = Path(base_dir)
        self.files = []
        self.groups = []
    def scan_files(self, recursive: bool = True, extensions: List[str] = None):
        """扫描文件"""
        pattern = '**/*' if recursive else '*'
        for filepath in self.base_dir.glob(pattern):
            if filepath.is_file():
                if extensions:
                    if filepath.suffix.lower() in [ext.lower() for ext in extensions]:
                        self.files.append(filepath)
                else:
                    self.files.append(filepath)
        print(f"找到 {len(self.files)} 个文件")
        return self.files
    def get_name_components(self, filepath: Path) -> Dict:
        """获取文件名组件"""
        name = filepath.stem
        return {
            'full': str(filepath),
            'name': name,
            'extension': filepath.suffix,
            'clean': re.sub(r'[\d\-_\(\)\[\]]+', ' ', name).strip().lower(),
            'prefix': name[:3] if len(name) >= 3 else name,
            'suffix': name[-3:] if len(name) >= 3 else name,
            'numbers': re.findall(r'\d+', name),
            'length': len(name)
        }
    def fuzzy_match(self, threshold: float = 0.6) -> List[List[Path]]:
        """模糊匹配相似文件名"""
        groups = []
        processed = set()
        for i, file1 in enumerate(self.files):
            if file1 in processed:
                continue
            comp1 = self.get_name_components(file1)
            group = [file1]
            for file2 in self.files[i+1:]:
                if file2 in processed:
                    continue
                comp2 = self.get_name_components(file2)
                # 计算多个维度的相似度
                similarities = [
                    SequenceMatcher(None, comp1['clean'], comp2['clean']).ratio(),
                    SequenceMatcher(None, comp1['name'], comp2['name']).ratio()
                ]
                # 加权平均
                similarity = sum(similarities) / len(similarities)
                if similarity >= threshold:
                    group.append(file2)
                    processed.add(file2)
            if len(group) >= 2:
                groups.append(group)
                processed.add(file1)
        self.groups = groups
        return groups
    def regex_match(self, pattern: str) -> List[List[Path]]:
        """正则表达式匹配"""
        groups = defaultdict(list)
        for filepath in self.files:
            name = filepath.stem
            match = re.search(pattern, name)
            if match:
                key = match.group()
                groups[key].append(filepath)
        self.groups = [group for group in groups.values() if len(group) >= 2]
        return self.groups
    def content_based_match(self) -> List[List[Path]]:
        """基于文件内容的匹配(MD5)"""
        groups = defaultdict(list)
        for filepath in self.files:
            try:
                with open(filepath, 'rb') as f:
                    content = f.read()
                    md5 = hashlib.md5(content).hexdigest()
                    groups[md5].append(filepath)
            except Exception as e:
                print(f"读取文件出错 {filepath}: {e}")
        self.groups = [group for group in groups.values() if len(group) >= 2]
        return self.groups
    def export_results(self, output_file: str = "matches.json"):
        """导出匹配结果"""
        results = []
        for idx, group in enumerate(self.groups, 1):
            group_data = {
                'group_id': idx,
                'size': len(group),
                'files': [str(f) for f in group],
                'common_prefix': os.path.commonprefix([f.stem for f in group])
            }
            results.append(group_data)
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump({
                'scan_time': datetime.datetime.now().isoformat(),
                'base_directory': str(self.base_dir),
                'total_groups': len(results),
                'groups': results
            }, f, ensure_ascii=False, indent=2)
        print(f"结果已导出到 {output_file}")
    def batch_rename(self, dry_run: bool = True):
        """批量重命名操作"""
        for idx, group in enumerate(self.groups, 1):
            print(f"\n组 {idx}:")
            for filepath in group:
                # 生成新文件名
                parent = filepath.parent
                old_name = filepath.stem
                ext = filepath.suffix
                # 添加组编号前缀
                new_name = f"Group{idx:03d}_{old_name}{ext}"
                new_path = parent / new_name
                if dry_run:
                    print(f"  将重命名: {filepath.name} -> {new_name}")
                else:
                    filepath.rename(new_path)
                    print(f"  已重命名: {filepath.name} -> {new_name}")
def main():
    """主函数"""
    import argparse
    parser = argparse.ArgumentParser(description='高级文件名匹配工具')
    parser.add_argument('directory', help='要扫描的目录')
    parser.add_argument('-m', '--mode', choices=['fuzzy', 'regex', 'content'],
                      default='fuzzy', help='匹配模式')
    parser.add_argument('-t', '--threshold', type=float, default=0.6,
                      help='模糊匹配阈值')
    parser.add_argument('-p', '--pattern', help='正则表达式模式')
    parser.add_argument('-e', '--extensions', nargs='+', help='文件扩展名')
    parser.add_argument('--no-recursive', action='store_true',
                      help='不递归扫描')
    parser.add_argument('--export', help='导出结果到JSON文件')
    parser.add_argument('--rename', action='store_true', help='执行重命名')
    parser.add_argument('--dry-run', action='store_true', help='仅预览不执行')
    args = parser.parse_args()
    # 创建匹配器
    matcher = FileNameMatcher(args.directory)
    # 扫描文件
    matcher.scan_files(recursive=not args.no_recursive, extensions=args.extensions)
    # 执行匹配
    if args.mode == 'fuzzy':
        print(f"执行模糊匹配 (阈值: {args.threshold})")
        groups = matcher.fuzzy_match(args.threshold)
    elif args.mode == 'regex':
        if not args.pattern:
            print("错误: 正则模式需要提供 -p 参数")
            sys.exit(1)
        print(f"执行正则匹配 (模式: {args.pattern})")
        groups = matcher.regex_match(args.pattern)
    elif args.mode == 'content':
        print("执行内容匹配 (MD5)")
        groups = matcher.content_based_match()
    # 显示结果
    print(f"\n找到 {len(groups)} 组匹配:\n")
    for idx, group in enumerate(groups, 1):
        print(f"组 {idx} ({len(group)} 个文件):")
        common = os.path.commonprefix([f.stem for f in group])
        if common:
            print(f"  共同前缀: {common}")
        for filepath in group:
            print(f"  - {filepath}")
        print()
    # 导出结果
    if args.export:
        matcher.export_results(args.export)
    # 执行重命名
    if args.rename:
        print("开始重命名操作...")
        matcher.batch_rename(dry_run=args.dry_run)
if __name__ == "__main__":
    main()

使用方法

Python版本使用示例:

# 基本使用 - 扫描当前目录
python filename_matcher.py /path/to/directory
# 设置相似度阈值 (0-1)
python filename_matcher.py /path/to/directory -t 0.8
# 递归扫描子目录
python filename_matcher.py /path/to/directory -r
# 只扫描特定扩展名
python filename_matcher.py /path/to/directory -e .jpg .png .mp4
# 正则表达式匹配
python filename_matcher.py /path/to/directory -m regex -p "IMG_\d{4}"
匹配(查重)
python filename_matcher.py /path/to/directory -m content
# 导出结果到JSON
python filename_matcher.py /path/to/directory --export results.json
# 批量重命名(预览)
python filename_matcher.py /path/to/directory --rename --dry-run

Bash版本使用示例:

# 基本使用
chmod +x filename_matcher.sh
./filename_matcher.sh /path/to/directory
# 设置阈值(0-1)
./filename_matcher.sh /path/to/directory 0.8
# 递归扫描
./filename_matcher.sh /path/to/directory 0.6 true

功能特点

  1. 多种匹配模式

    • 模糊匹配(基于字符串相似度)
    • 正则表达式匹配匹配(MD5哈希)
  2. 灵活配置

    • 可调整相似度阈值
    • 支持文件扩展名筛选
    • 递归/非递归扫描
  3. 实用功能

    • 分组显示匹配结果
    • 导出结果到JSON
    • 批量重命名操作
    • 预览模式(dry-run)
  4. 智能化

    • 自动清理文件名(去除编号和标点)
    • 多维度相似度计算
    • 共同前缀识别

这个脚本非常适合整理下载文件夹、照片库、文档仓库等场景,帮助你快速发现和处理相似文件名的文件。

抱歉,评论功能暂时关闭!