← 中文资源

简体中文版本

按扩展名递归搜索文件

遍历目录树时不跟随符号链接,跳过隐藏条目,只收集扩展名匹配的普通文件并返回路径列表。

阅读英文原文

功能说明

代码生成、清理脚本和仓库检查经常需要按扩展名收集文件。递归读取目录时要处理几个边界:隐藏目录通常应跳过,符号链接可能指回上级形成环,目录项读取失败不应让整个遍历崩溃。walkdir 提供迭代器风格的遍历,支持控制是否跟随链接,并允许用 filter_entry 在进入目录前剪枝。

本例从根路径开始,跳过除根之外以点开头的条目,只保留普通文件,再比较扩展名。返回显示路径的字符串,便于打印和记录。主程序统计当前目录下的 Rust 文件并只展示前五个,避免输出过长。

关键代码

is_hidden 把文件名转换为字符串,无法转换时按非隐藏处理,避免因非 UTF-8 文件名漏掉内容。find_by_extension 关闭 follow_links,防止符号链接形成无限递归;filter_entry 对目录和文件同时生效,深度为零的根始终保留。filter_map(Result::ok) 丢弃读取失败的条目,生产脚本若需要诊断应记录错误而不是静默丢弃。file_type().is_file 排除目录、链接和设备文件,扩展名比较区分大小写。

使用注意

扩展名比较是字节级且区分大小写,Rs 与 rs 不会匹配,需要时应统一转小写。隐藏规则按文件名首字符判断,Windows 的隐藏属性不会反映在名称上。符号链接本身被跳过,若链接指向的文件需要处理,应显式开启跟随并自行防止环。遍历大目录应在调用方限制深度或使用并行遍历。返回所有路径会占用内存,超大目录可改为回调或迭代器。

验证方式

在包含若干 Rust 文件与隐藏目录的项目根运行脚本,前五行列出匹配路径,最后一行为总数。创建 .hidden/demo.rs 后重跑,计数不应增加,说明隐藏目录被剪枝。创建指向父目录的符号链接,遍历仍能结束,说明未跟随链接。

代码示例

use std::path::Path;
use walkdir::{DirEntry, WalkDir};

fn is_hidden(entry: &DirEntry) -> bool {
    entry
        .file_name()
        .to_str()
        .map(|name| name.starts_with('.'))
        .unwrap_or(false)
}

fn find_by_extension(root: &Path, extension: &str) -> Vec<String> {
    WalkDir::new(root)
        .follow_links(false)
        .into_iter()
        .filter_entry(|entry| entry.depth() == 0 || !is_hidden(entry))
        .filter_map(Result::ok)
        .filter(|entry| entry.file_type().is_file())
        .filter(|entry| {
            entry.path().extension().and_then(|ext| ext.to_str()) == Some(extension)
        })
        .map(|entry| entry.path().display().to_string())
        .collect()
}

fn main() {
    let matches = find_by_extension(Path::new("."), "rs");
    for path in matches.iter().take(5) {
        println!("{path}");
    }
    println!("total: {}", matches.len());
}