功能说明
代码生成、清理脚本和仓库检查经常需要按扩展名收集文件。递归读取目录时要处理几个边界:隐藏目录通常应跳过,符号链接可能指回上级形成环,目录项读取失败不应让整个遍历崩溃。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());
}