C#生成文件清单命令行工具(DeepSeek版)



FileList 是 DeepSeek 编写的 Windows 控制台工具,用于快速生成指定文件夹下的文件清单,并将结果保存为 FileList.txt 文件。它支持两种输出模式:

■ 树形模式(默认) —— 以树状结构展示目录和文件,清晰直观。
■ 内容模式 —— 输出每个文件的完整路径和文本内容,便于批量查看或搜索。

支持按扩展名过滤(包含或排除),自动跳过非文本文件或超大文件,完美兼容命令行和双击运行两种使用场景。

命令行格式

引用内容 引用内容
FileList.exe [文件夹路径] [包含扩展名] [排除扩展名] [是否显示内容]

调用示例:

引用内容 引用内容
REM 查看帮助
FileList.exe /?

REM 列出当前目录树
FileList.exe

REM 列出 D:\Project 下所有 .cs 和 .csproj 文件(树形)
FileList.exe "D:\Project" ".cs|.csproj"

REM 排除 .tmp 和 .log 文件,并显示文件内容
FileList.exe "D:\Project" "" ".tmp|.log" true

程序代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management;
using System.Text;

namespace FileList
{
    class Program
    {
        /// <summary>
        /// 常见文本文件扩展名集合,用于内容模式判断文件是否为文本类型。
        /// 不区分大小写。
        /// </summary>
        static readonly HashSet<string> TextExtensions = new HashSet<string>(
            StringComparer.OrdinalIgnoreCase)
        {
            ".txt", ".md", ".markdown", ".html", ".htm", ".css", ".js", ".ts", ".tsx", ".jsx",
            ".json", ".xml", ".csv", ".config", ".cs", ".vb", ".cpp", ".c", ".h", ".hpp",
            ".py", ".php", ".asp", ".aspx", ".ini", ".log", ".bat", ".cmd", ".sh", ".yml",
            ".yaml", ".sql", ".svg", ".rb", ".java", ".pl", ".pm", ".go", ".rs", ".toml",
            ".lock", ".gitignore", ".gitattributes", ".editorconfig", ".env", ".proto"
        };

        /// <summary>
        /// 程序入口。支持4个位置参数,按顺序为:
        /// 1. 文件夹路径(空则当前目录)
        /// 2. 包含扩展名(|分隔)
        /// 3. 排除扩展名(|分隔)
        /// 4. 是否显示文件内容(true/false)
        /// </summary>
        static int Main(string[] args)
        {
            // 检测当前运行环境是否为 cmd 或 powershell,决定最终是否等待按键
            bool isCmdEnv = IsRunningFromCmd();

            try
            {
                // 处理帮助请求
                if (args.Length >= 1 && !string.IsNullOrEmpty(args[0]) &&
                    (args[0] == "/?" || args[0].Equals("-h", StringComparison.OrdinalIgnoreCase) ||
                     args[0].Equals("--help", StringComparison.OrdinalIgnoreCase)))
                {
                    ShowHelp();
                    if (!isCmdEnv) WaitForKey();
                    return 0;
                }

                // 解析第一个参数:文件夹路径
                string folderPath = null;
                if (args.Length >= 1 && !string.IsNullOrEmpty(args[0]))
                    folderPath = args[0];

                if (string.IsNullOrEmpty(folderPath))
                    folderPath = AppDomain.CurrentDomain.BaseDirectory; // 默认为 exe 所在目录

                // 解析第二个参数:包含扩展名列表
                string[] includeExts = null;
                if (args.Length >= 2 && !string.IsNullOrEmpty(args[1]))
                    includeExts = NormalizeExtensions(
                        args[1].Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries));

                // 解析第三个参数:排除扩展名列表
                string[] excludeExts = null;
                if (args.Length >= 3 && !string.IsNullOrEmpty(args[2]))
                    excludeExts = NormalizeExtensions(
                        args[2].Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries));

                // 解析第四个参数:是否显示文件内容
                bool showContent = false;
                if (args.Length >= 4 && !string.IsNullOrEmpty(args[3]))
                {
                    if (!bool.TryParse(args[3], out showContent))
                    {
                        Console.Error.WriteLine("Error: fourth parameter must be 'true' or 'false'.");
                        if (!isCmdEnv) WaitForKey();
                        return 1;
                    }
                }

                // 检查目录是否存在
                if (!Directory.Exists(folderPath))
                {
                    Console.Error.WriteLine($"Error: directory not found: {folderPath}");
                    if (!isCmdEnv) WaitForKey();
                    return 1;
                }

                // 检查输出路径是否是已存在的目录
                string outputFile = Path.Combine(folderPath, "FileList.txt");
                if (Directory.Exists(outputFile))
                {
                    Console.Error.WriteLine($"Error: '{outputFile}' is a directory, cannot write.");
                    if (!isCmdEnv) WaitForKey();
                    return 1;
                }

                // 根据模式生成清单
                if (showContent)
                    GenerateContentList(folderPath, outputFile, includeExts, excludeExts);
                else
                    GenerateTreeList(folderPath, outputFile, includeExts, excludeExts);

                Console.WriteLine("FileList.txt created successfully.");
                if (!isCmdEnv) WaitForKey();
                return 0;
            }
            catch (UnauthorizedAccessException ex)
            {
                Console.Error.WriteLine($"Error: access denied - {ex.Message}");
                if (!isCmdEnv) WaitForKey();
                return 1;
            }
            catch (IOException ex)
            {
                Console.Error.WriteLine($"Error: I/O error - {ex.Message}");
                if (!isCmdEnv) WaitForKey();
                return 1;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"Error: {ex.Message}");
                if (!isCmdEnv) WaitForKey();
                return 1;
            }
        }

        /// <summary>
        /// 显示命令行帮助信息。
        /// </summary>
        static void ShowHelp()
        {
            Console.WriteLine("FileList - List files in a directory tree.");
            Console.WriteLine("Usage:");
            Console.WriteLine("  FileList.exe [folderPath] [includeExts] [excludeExts] [showContent]");
            Console.WriteLine();
            Console.WriteLine("Parameters:");
            Console.WriteLine("  folderPath   : Target directory. Default: current directory.");
            Console.WriteLine("  includeExts  : Use '|' separated extensions (e.g. .cs|.php).");
            Console.WriteLine("                 If provided, only these extensions are listed. (ignore excludeExts)");
            Console.WriteLine("  excludeExts  : Extensions to exclude (e.g. .tmp|.log).");
            Console.WriteLine("  showContent  : 'true' to output file paths and content, 'false' for tree view.");
            Console.WriteLine();
            Console.WriteLine("Notes:");
            Console.WriteLine("  - In CMD, escape '|' with ^ or use quotes: \".cs|.php\"");
            Console.WriteLine("  - Extensions are case-insensitive and may be specified with or without leading dot.");
        }

        /// <summary>
        /// 标准化扩展名数组:去除空白,保证每个扩展名以点开头。
        /// </summary>
        static string[] NormalizeExtensions(string[] exts)
        {
            for (int i = 0; i < exts.Length; i++)
            {
                exts[i] = exts[i].Trim();
                if (!exts[i].StartsWith("."))
                    exts[i] = "." + exts[i];
            }
            return exts;
        }

        /// <summary>
        /// 通过 WMI 查询父进程,判断是否由 cmd 或 powershell 启动。
        /// 如果判断失败则默认按独立运行处理(返回 false)。
        /// </summary>
        static bool IsRunningFromCmd()
        {
            try
            {
                using (var current = Process.GetCurrentProcess())
                {
                    string query = $"Select ParentProcessId FROM Win32_Process Where ProcessId = {current.Id}";
                    using (var searcher = new ManagementObjectSearcher(query))
                    {
                        foreach (var obj in searcher.Get())
                        {
                            int parentId = Convert.ToInt32(obj["ParentProcessId"]);
                            var parent = Process.GetProcessById(parentId);
                            string name = parent.ProcessName.ToLower();
                            return name == "cmd" || name == "powershell";
                        }
                    }
                }
            }
            catch
            {
                // 无法查询时按独立运行处理
            }
            return false;
        }

        /// <summary>
        /// 独立运行时等待用户按键,防止窗口立即关闭。
        /// </summary>
        static void WaitForKey()
        {
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }

        /// <summary>
        /// 生成树形结构的文件列表(不含文件内容)。
        /// </summary>
        static void GenerateTreeList(string rootPath, string outputFile, string[] includeExts, string[] excludeExts)
        {
            // 构建根节点
            var root = new TreeNode
            {
                Name = rootPath,
                IsDirectory = true,
                FullPath = rootPath
            };

            // 递归构建树,并过滤掉无匹配文件的空目录
            BuildTree(root, includeExts, excludeExts);

            var sb = new StringBuilder();
            RenderTree(root, "", true, sb); // 渲染成字符串
            File.WriteAllText(outputFile, sb.ToString(), Encoding.UTF8);
        }

        /// <summary>
        /// 生成含文件内容的列表(扁平,无目录树)。
        /// </summary>
        static void GenerateContentList(string rootPath, string outputFile, string[] includeExts, string[] excludeExts)
        {
            // 获取所有符合过滤条件的文件,并按完整路径排序(不区分大小写)
            var files = GetFilteredFiles(rootPath, includeExts, excludeExts);
            files.Sort(StringComparer.OrdinalIgnoreCase);

            using (var writer = new StreamWriter(outputFile, false, Encoding.UTF8))
            {
                foreach (var filePath in files)
                {
                    writer.WriteLine(filePath + ":");

                    try
                    {
                        var fileInfo = new FileInfo(filePath);
                        const long maxSize = 1 * 1024 * 1024; // 1 MB

                        // 文件过大则跳过内容
                        if (fileInfo.Length > maxSize)
                        {
                            writer.WriteLine("[File size exceeds 1MB, content not displayed]");
                        }
                        // 非文本文件(根据扩展名)跳过内容
                        else if (!IsTextFile(filePath))
                        {
                            writer.WriteLine("[Non-text file, content not displayed]");
                        }
                        else
                        {
                            // 读取文件全部文本(UTF-8)
                            string content = File.ReadAllText(filePath, Encoding.UTF8);
                            writer.WriteLine(content);
                        }
                    }
                    catch (UnauthorizedAccessException)
                    {
                        throw; // 权限错误直接抛出,终止程序
                    }
                    catch (IOException)
                    {
                        throw;
                    }

                    writer.WriteLine("------------------------------------------------------------");
                }
            }
        }

        /// <summary>
        /// 判断指定文件是否属于文本文件(基于扩展名)。
        /// </summary>
        static bool IsTextFile(string filePath)
        {
            string ext = Path.GetExtension(filePath);
            return TextExtensions.Contains(ext);
        }

        /// <summary>
        /// 递归构建树节点,并根据过滤条件判断是否保留该节点。
        /// 返回 true 表示该节点(目录)下至少有一个匹配文件,应保留。
        /// </summary>
        static bool BuildTree(TreeNode node, string[] includeExts, string[] excludeExts)
        {
            // 如果是文件,直接判断是否符合过滤条件
            if (!node.IsDirectory)
                return IsFileIncluded(node.FullPath, includeExts, excludeExts);

            var dirInfo = new DirectoryInfo(node.FullPath);
            FileSystemInfo[] items;
            try
            {
                items = dirInfo.GetFileSystemInfos(); // 包含隐藏文件和系统文件
            }
            catch (UnauthorizedAccessException)
            {
                throw; // 无权访问时直接抛出,由上层终止程序
            }

            var children = new List<TreeNode>();
            foreach (var item in items)
            {
                var child = new TreeNode
                {
                    Name = item.Name,
                    FullPath = item.FullName,
                    IsDirectory = (item.Attributes & FileAttributes.Directory) == FileAttributes.Directory
                };

                // 递归检查子项,只有包含匹配文件的子项才加入列表
                bool hasFiles = BuildTree(child, includeExts, excludeExts);
                if (hasFiles)
                    children.Add(child);
            }

            // 同级条目(文件+目录)按字母升序,不区分大小写
            children.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
            node.Children = children;

            // 该目录是否保留:只要至少有一个子节点(文件或包含文件的子目录)
            return children.Count > 0;
        }

        /// <summary>
        /// 判断单个文件是否符合包含/排除扩展名规则。
        /// 如果指定了包含列表,则文件扩展名必须在列表中;
        /// 否则,如果指定了排除列表,文件扩展名不能在其中;
        /// 都未指定则匹配所有文件。
        /// </summary>
        static bool IsFileIncluded(string filePath, string[] includeExts, string[] excludeExts)
        {
            string ext = Path.GetExtension(filePath);
            if (includeExts != null && includeExts.Length > 0)
                return includeExts.Any(e => string.Equals(e, ext, StringComparison.OrdinalIgnoreCase));
            if (excludeExts != null && excludeExts.Length > 0)
                return !excludeExts.Any(e => string.Equals(e, ext, StringComparison.OrdinalIgnoreCase));
            return true;
        }

        /// <summary>
        /// 递归获取指定目录下所有符合过滤条件的文件完整路径(扁平列表)。
        /// </summary>
        static List<string> GetFilteredFiles(string rootPath, string[] includeExts, string[] excludeExts)
        {
            var result = new List<string>();
            var dirInfo = new DirectoryInfo(rootPath);
            FileSystemInfo[] items;
            try
            {
                items = dirInfo.GetFileSystemInfos();
            }
            catch (UnauthorizedAccessException)
            {
                throw;
            }

            foreach (var item in items)
            {
                if ((item.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                {
                    try
                    {
                        // 递归添加子目录中的文件
                        result.AddRange(GetFilteredFiles(item.FullName, includeExts, excludeExts));
                    }
                    catch (UnauthorizedAccessException)
                    {
                        throw;
                    }
                }
                else
                {
                    if (IsFileIncluded(item.FullName, includeExts, excludeExts))
                        result.Add(item.FullName);
                }
            }
            return result;
        }

        /// <summary>
        /// 递归渲染树形结构到 StringBuilder。
        /// </summary>
        /// <param name="node">当前节点</param>
        /// <param name="indent">当前行的缩进前缀</param>
        /// <param name="isLast">当前节点是否为同级最后一个(决定使用 └── 还是 ├──)</param>
        /// <param name="sb">输出缓冲</param>
        static void RenderTree(TreeNode node, string indent, bool isLast, StringBuilder sb)
        {
            // 根节点特殊处理:直接输出完整路径,不加树形符号
            if (node.IsDirectory && node.Name == node.FullPath)
            {
                sb.AppendLine(node.Name);
            }
            else
            {
                sb.Append(indent);
                sb.Append(isLast ? "└──" : "├──");
                sb.AppendLine(node.Name);
            }

            // 处理子节点
            if (node.IsDirectory && node.Children != null)
            {
                for (int i = 0; i < node.Children.Count; i++)
                {
                    bool lastChild = (i == node.Children.Count - 1);

                    // 根节点的直接子节点缩进为空(不产生多余空格)
                    string childIndent;
                    if (node.Name == node.FullPath)
                    {
                        childIndent = ""; // 根下一级无额外缩进
                    }
                    else
                    {
                        childIndent = indent + (isLast ? "    " : "│   ");
                    }

                    RenderTree(node.Children[i], childIndent, lastChild, sb);
                }
            }
        }

        /// <summary>
        /// 树节点,表示文件或目录。
        /// </summary>
        class TreeNode
        {
            public string Name;           // 文件/目录名
            public string FullPath;       // 完整路径
            public bool IsDirectory;      // 是否为目录
            public List<TreeNode> Children; // 子节点列表(仅目录有效)
        }
    }
}

运行效果:

引用内容 引用内容
E:\Web
├──AIAPP.md
├──index.html
├──package-lock.json
├──package.json
├──postcss.config.js
├──src
│   ├──app.tsx
│   ├──components
│   │   ├──ImagePreview.tsx
│   │   └──Layout.tsx
│   ├──context
│   │   └──AuthContext.tsx
│   ├──index.css
│   ├──index.tsx
│   ├──lib
│   │   ├──captcha.ts
│   │   ├──image-compressor.ts
│   │   ├──supabase.ts
│   │   └──utils.ts
│   ├──pages
│   │   ├──AdminListPage.tsx
│   │   ├──CustomerListPage tbody.txt
│   │   ├──CustomerListPage.tsx
│   │   ├──CustomerListPage.tsx.bak
│   │   ├──DashboardPage.tsx
│   │   ├──InitPage.tsx
│   │   ├──ItemListPage.tsx
│   │   ├──LoginPage.tsx
│   │   └──PublicCustomerRepairsPage.tsx
│   └──types
│       └──database.ts
├──supabase
│   ├──migration
│   │   ├──001_create_customer_share_tokens.sql
│   │   ├──002_truncate_tables.sql
│   │   ├──003_fix_latitude_longitude_type.sql
│   │   ├──004_add_admin_login_security.sql
│   │   ├──005_add_administrator_to_repair_items.sql
│   │   ├──006_change_images_to_single_url.sql
│   │   ├──007_update_admin_roles_to_three_levels.sql
│   │   ├──add_admin_login_fields.sql
│   │   └──fix_last_login_ip_length.sql
│   └──tables
│       └──schema.sql
├──tailwind.config.js
├──tsconfig.json
├──tsconfig.node.json
└──vite.config.ts

相关链接

[1].FileList开发文档:https://www.mzwu.com/doc/FileList.pdf

评论: 0 | 引用: 0 | 查看次数: 158
发表评论
登录后再发表评论!