以土豆之名,行学习之实

文件内容复制工具


文件内容复制工具 - 技术文档

概述

file_content_copy.py 是一个用于批量处理项目文件的Python工具,能够将指定目录下多种文件类型的內容整合到单个输出文件中,同时生成清晰的项目目录结构。

功能特性

核心功能

  • 多格式文件收集: 支持同时处理多种文件扩展名(.py, .txt, .md, .js, .css, .html等)

  • 智能目录排除: 自动排除常见的开发环境目录和缓存目录

  • 目录结构生成: 自动生成树形结构的项目目录概览

  • 编码自动检测: 支持多种编码格式(UTF-8, GBK, Latin-1)

  • 文件大小限制: 可配置最大文件处理大小,避免处理过大文件

安全特性

  • 权限处理: 完善的异常处理,避免因权限问题导致程序崩溃

  • 路径安全: 使用相对路径检查,确保文件在项目目录内

  • 隐藏文件过滤: 自动跳过以.开头的隐藏文件和目录

技术架构

模块结构

file_content_copy.py
├── get_directory_structure()    # 目录树生成器
├── copy_files_by_extension()    # 主处理函数
└── 主程序入口


依赖库

  • os: 操作系统接口

  • glob: 文件模式匹配

  • pathlib: 面向对象的文件系统路径操作

核心函数详解

1. get_directory_structure(directory, all_files, exclude_dirs=None)

功能: 生成格式化的目录结构树

参数:

  • directory: 目标目录路径

  • all_files: 要包含的文件列表

  • exclude_dirs: 排除目录列表


2. copy_files_by_extension(directory, extensions, exclude_dirs, output_dirname, max_file_size)

主处理函数参数:

参数类型默认值描述
directorystr必需要处理的根目录路径
extensionslist必需文件扩展名列表 ['.py', '.txt']
exclude_dirslist[]排除目录列表
output_dirnamestr"output_files"输出目录名称
max_file_sizeint10MB最大文件大小限制(字节)

文件命名规则

  • 每种文件类型生成独立的输出文件

  • 命名格式: all_{ext_name}_files.txt

  • 示例: all_py_files.txt, all_md_files.txt

错误处理机制

异常类型处理

  1. 文件编码错误: 尝试多种编码方案

  2. 权限错误: 跳过无法访问的文件/目录

  3. 路径错误: 检查文件是否在项目目录内

  4. 大小限制: 跳过超过设定大小的文件

日志输出

  • 处理进度实时显示

  • 错误和警告信息明确

  • 最终统计报告

应用场景

代码审查

  • 快速收集项目所有源代码文件

  • 生成完整的代码快照

项目文档

  • 自动生成项目结构文档

  • 代码片段收集和整理

备份归档

  • 选择性备份特定类型文件

  • 创建轻量级项目快照

性能考虑

优化措施

  • 使用集合(Set)进行快速文件查找

  • 递归目录遍历避免重复扫描

  • 文件大小预检查避免内存溢出

限制配置

  • 默认10MB文件大小限制

  • 可调整的排除规则

  • 灵活的扩展名配置

扩展建议

可能的增强功能

  1. 压缩输出: 自动压缩输出文件

  2. 模式匹配: 支持正则表达式文件匹配

  3. 增量处理: 只处理修改过的文件

  4. 远程存储: 支持输出到云存储

这个工具特别适合需要快速收集和分析项目文件结构的开发场景,如代码审查、项目迁移或文档生成等任务。


附:代码

import os
import glob
from pathlib import Path


def get_directory_structure(directory, all_files, exclude_dirs=None):
    """
    生成目录结构树
    """
    if exclude_dirs is None:
        exclude_dirs = []

    project_root = Path(directory).resolve()
    structure = ["#" * 50 + "
",
                 "# 项目目录结构:
",
                 "#" * 50 + "
"]

    # 安全地创建文件路径集合
    file_set = set()
    for f in all_files:
        file_path = Path(f)
        try:
            relative_path = file_path.relative_to(project_root)

            # 检查文件是否在排除目录中
            skip = any(
                str(relative_path) == str(Path(exclude_dir)) or
                str(relative_path).startswith(str(Path(exclude_dir)) + os.sep)
                for exclude_dir in exclude_dirs
            )
            if skip:
                continue

            file_set.add(relative_path)
        except ValueError:
            print(f"警告: 文件 {f} 不在项目目录内,已跳过")
            continue

    def build_tree(current_path, prefix=""):
        """递归构建目录树"""
        # 检查当前目录是否在排除列表中
        current_rel_path = current_path.relative_to(project_root)
        if any(
                str(current_rel_path) == str(Path(exclude_dir)) or
                str(current_rel_path).startswith(str(Path(exclude_dir)) + os.sep)
                for exclude_dir in exclude_dirs
        ):
            return

        items = []

        try:
            for item in sorted(current_path.iterdir()):
                if item.name.startswith('.'):  # 跳过隐藏文件/目录
                    continue

                # 检查是否在排除目录中
                item_rel_path = item.relative_to(project_root)
                skip_item = any(
                    str(item_rel_path) == str(Path(exclude_dir)) or
                    str(item_rel_path).startswith(str(Path(exclude_dir)) + os.sep)
                    for exclude_dir in exclude_dirs
                )
                if skip_item:
                    continue

                if item.is_dir():
                    # 检查该目录下是否有目标文件
                    has_target_files = any(
                        str(file_path).startswith(str(item.relative_to(project_root)))
                        for file_path in file_set
                    )
                    if has_target_files:
                        items.append((item, 'dir'))
                else:
                    rel_path = item.relative_to(project_root)
                    if rel_path in file_set:
                        items.append((item, 'file'))
        except (PermissionError, OSError) as e:
            print(f"无法访问目录 {current_path}: {e}")
            return

        for index, (item, item_type) in enumerate(items):
            is_last = index == len(items) - 1
            connector = "└── " if is_last else "├── "

            if item_type == 'dir':
                structure.append(f"{prefix}{connector}{item.name}/
")
                new_prefix = prefix + ("    " if is_last else "│   ")
                build_tree(item, new_prefix)
            else:  # file
                structure.append(f"{prefix}{connector}{item.name}
")

    # 从项目根目录开始构建
    structure.append(f"{project_root.name}/
")
    build_tree(project_root, "")

    structure.append("
" + "#" * 50 + "

")
    return "".join(structure)


def copy_files_by_extension(directory, extensions, exclude_dirs=None,
                            output_dirname="output_files", max_file_size=10 * 1024 * 1024):
    """
    将目录下指定后缀的文件内容复制到单个文件中,每种后缀生成一个文件

    Args:
        directory: 要处理的根目录路径
        extensions: 要处理的文件扩展名列表
        exclude_dirs: 要排除的目录列表
        output_dirname: 输出目录名称
        max_file_size: 最大文件大小限制(字节)
    """
    if exclude_dirs is None:
        exclude_dirs = []

    directory = os.path.abspath(directory)
    project_root = Path(directory).resolve()

    # 验证目录存在
    if not project_root.exists():
        print(f"错误: 目录 {directory} 不存在")
        return

    # 创建输出目录
    output_dir = project_root / output_dirname
    output_dir.mkdir(exist_ok=True)

    # 将输出目录添加到排除列表中
    if output_dirname not in exclude_dirs:
        exclude_dirs.append(output_dirname)

    # 获取所有指定后缀的文件
    all_files = []
    for ext in extensions:
        pattern = f"**/*{ext}" if not ext.startswith('.') else f"**/*{ext}"
        ext_files = [str(path) for path in project_root.glob(pattern)]
        all_files.extend(ext_files)

    if not all_files:
        print(f"未找到任何 {extensions} 文件")
        return

    print(f"找到 {len(all_files)} 个目标文件")

    # 为每种后缀创建一个输出文件
    for ext in extensions:
        pattern = f"**/*{ext}" if not ext.startswith('.') else f"**/*{ext}"
        ext_files = [str(path) for path in project_root.glob(pattern)]

        # 过滤排除目录中的文件
        filtered_files = []
        for file_path in ext_files:
            file_obj = Path(file_path)
            try:
                relative_path = file_obj.relative_to(project_root)

                # 检查排除条件
                skip = any(
                    str(relative_path) == str(Path(exclude_dir)) or
                    str(relative_path).startswith(str(Path(exclude_dir)) + os.sep)
                    for exclude_dir in exclude_dirs
                )

                if not skip:
                    # 检查文件大小
                    try:
                        file_size = file_obj.stat().st_size
                        if file_size > max_file_size:
                            print(f"跳过大文件: {relative_path} ({file_size // 1024}KB)")
                            continue
                        filtered_files.append(file_path)
                    except OSError as e:
                        print(f"无法获取文件大小 {relative_path}: {e}")

            except ValueError:
                print(f"警告: 文件 {file_path} 不在项目目录内,已跳过")
                continue

        if not filtered_files:
            print(f"未找到任何 {ext} 文件(排除后)")
            continue

        # 创建输出文件
        ext_name = ext.lstrip('.')
        output_file = output_dir / f"all_{ext_name}_files.txt"

        try:
            with open(output_file, 'w', encoding='utf-8') as outfile:
                # 写入目录结构
                try:
                    directory_structure = get_directory_structure(directory, all_files, exclude_dirs)
                    outfile.write(directory_structure)
                except Exception as e:
                    print(f"生成目录结构时出错: {e}")
                    outfile.write("目录结构生成失败

")

                # 写入文件内容
                processed_files = 0
                for file_path in filtered_files:
                    file_obj = Path(file_path)

                    try:
                        relative_path = file_obj.relative_to(project_root)
                    except ValueError:
                        relative_path = file_obj

                    # 写入文件分隔符
                    outfile.write(f"
{'#' * 80}
")
                    outfile.write(f"# 文件: {relative_path}
")
                    outfile.write(f"{'#' * 80}

")

                    try:
                        # 尝试多种编码
                        encodings = ['utf-8', 'gbk', 'latin-1']
                        content = None

                        for encoding in encodings:
                            try:
                                with open(file_path, 'r', encoding=encoding) as infile:
                                    content = infile.read()
                                break
                            except UnicodeDecodeError:
                                continue

                        if content is not None:
                            outfile.write(content)
                            outfile.write("

")
                            print(f"已处理: {relative_path}")
                            processed_files += 1
                        else:
                            outfile.write(f"无法读取文件: 编码问题

")
                            print(f"读取失败(编码问题): {relative_path}")

                    except Exception as e:
                        outfile.write(f"读取文件失败: {e}

")
                        print(f"读取失败: {relative_path} - {e}")

            print(f"成功处理 {processed_files}/{len(filtered_files)} 个 {ext} 文件,结果保存在: {output_file}")

        except Exception as e:
            print(f"创建输出文件失败 {output_file}: {e}")

    print(f"
所有输出文件已保存到: {output_dir}")


# 使用示例
if __name__ == "__main__":
    # 指定要处理的根目录路径
    root_directory = r"E:PythonProjectmysql_backup"  # 可以修改为任何目录路径

    # 指定要处理的后缀列表
    extensions = ['.py', '.txt', '.md', '.js', '.css', '.html']

    # 指定要排除的目录列表(相对于根目录的路径)
    exclude_directories = [
        "__pycache__",  # Python缓存目录
        ".git",  # Git目录
        "node_modules",  # Node.js依赖目录
        ".venv",  # Python虚拟环境
        "dist",  # 构建输出目录
        "build",  # 构建目录
        ".vscode",  # VSCode配置目录
        ".idea",  # PyCharm配置目录
        "project_files"  # 输出目录自身
    ]

    # 输出目录名称
    output_directory = ".project_files"

    # 调用函数处理文件
    copy_files_by_extension(
        directory=root_directory,
        extensions=extensions,
        exclude_dirs=exclude_directories,
        output_dirname=output_directory,
        max_file_size=10 * 1024 * 1024  # 10MB限制
    )