Rust实现文件查找功能及拓展
1. 配置与参数验证
在开始编写文件查找程序时,首先要确保配置字段能接受多个值。例如,运行以下命令可美化输出配置信息:
$ cargo run -- -t f l -n txt mp3 -- tests/inputs/a tests/inputs/d
输出结果如下:
Config {
dirs: [
"tests/inputs/a",
"tests/inputs/d",
],
names: Some(
[
txt,
mp3,
],
),
entry_types: Some(
[
File,
Link,
],
),
}
在解决程序的其余部分之前,确保程序能输出上述结果并通过至少
cargo test dies
测试是很重要的。
以下是
get_args
函数,用于处理用户输入的参数:
pub fn get_args() -> MyResult<Config> {
let matches = App::new("findr")
.version("0.1.0")
.author("Ken Youens-Clark <kyclark@gmail.com>")
.about("Rust find")
.arg(
Arg::with_name("dirs")
.value_name("DIR")
.help("Search directory")
.default_value(".")
.min_values(1),
)
.arg(
Arg::with_name("names")
.value_name("NAME")
.help("Name")
.short("n")
.long("name")
.takes_value(true)
.multiple(true),
)
.arg(
Arg::with_name("types")
.value_name("TYPE")
.help("Entry type")
.short("t")
.long("type")
.possible_values(&["f", "d", "l"])
.takes_value(true)
.multiple(true),
)
.get_matches();
let mut names = vec![];
if let Some(vals) = matches.values_of_lossy("names") {
for name in vals {
match Regex::new(&name) {
Ok(re) => names.push(re),
_ => {
return Err(From::from(format!(
"Invalid --name \"{}\"",
name
)))
}
}
}
}
let entry_types = matches.values_of_lossy("types").map(|vals| {
vals.iter()
.filter_map(|val| match val.as_str() {
"d" => Some(Dir),
"f" => Some(File),
"l" => Some(Link),
_ => None,
})
.collect()
});
Ok(Config {
dirs: matches.values_of_lossy("dirs").unwrap(),
names: if names.is_empty() { None } else { Some(names) },
entry_types,
})
}
该函数的具体操作步骤如下:
1. 使用
App
定义程序的基本信息,包括名称、版本、作者和描述。
2. 定义三个参数:
dirs
(搜索目录,至少一个值,默认值为
.
)、
names
(文件名,可接受零个或多个值)和
types
(条目类型,可接受零个或多个
f
、
d
或
l
)。
3. 处理
names
参数,将其转换为正则表达式。若正则表达式无效,则返回错误。
4. 处理
types
参数,将用户输入的字符串转换为对应的
EntryType
枚举值。
5. 返回配置信息,
names
若为空则为
None
,否则为
Some(names)
。
2. 查找所有匹配项
在验证用户输入的参数后,就可以开始查找符合条件的文件和目录了。这里使用
walkdir
crate 来递归搜索目录结构。
以下是最初的
run
函数:
pub fn run(config: Config) -> MyResult<()> {
for dirname in config.dirs {
for entry in WalkDir::new(dirname) {
println!("{}", entry?.path().display());
}
}
Ok(())
}
该函数的操作流程如下:
1. 遍历配置中的每个目录。
2. 对每个目录使用
WalkDir::new
递归遍历其内容。
3. 打印每个条目的路径。
然而,这个简单版本的程序存在一个问题:当遇到不存在的目录时,程序会崩溃。为了解决这个问题,我们可以在读取目录前先检查目录是否可读取:
pub fn run(config: Config) -> MyResult<()> {
for dirname in config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
for entry in WalkDir::new(dirname) {
println!("{}", entry?.path().display());
}
}
}
}
Ok(())
}
操作步骤如下:
1. 遍历配置中的每个目录。
2. 使用
fs::read_dir
尝试读取目录。若失败,则打印错误信息并跳过该目录。
3. 若读取成功,则递归遍历目录内容并打印路径。
接下来,我们需要根据用户指定的条目类型和文件名进行过滤。以下是改进后的
run
函数:
pub fn run(config: Config) -> MyResult<()> {
for dirname in config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
for entry in WalkDir::new(dirname) {
let entry = entry?;
if let Some(types) = &config.entry_types {
if !types.iter().any(|type_| match type_ {
Link => entry.path_is_symlink(),
Dir => entry.file_type().is_dir(),
File => entry.file_type().is_file(),
}) {
continue;
}
}
if let Some(names) = &config.names {
if !names.iter().any(|re| {
re.is_match(&entry.file_name().to_string_lossy())
}) {
continue;
}
}
println!("{}", entry.path().display());
}
}
}
}
Ok(())
}
操作步骤如下:
1. 遍历配置中的每个目录。
2. 检查目录是否可读取,若不可读取则打印错误信息并跳过。
3. 递归遍历目录内容。
4. 若用户指定了条目类型,则过滤不符合类型的条目。
5. 若用户指定了文件名,则过滤不匹配正则表达式的条目。
6. 打印符合条件的条目路径。
为了使代码更优雅,我们可以使用闭包和迭代器来重构
run
函数:
pub fn run(config: Config) -> MyResult<()> {
let type_filter = |entry: &DirEntry| match &config.entry_types {
Some(types) => types.iter().any(|t| match t {
Link => entry.path_is_symlink(),
Dir => entry.file_type().is_dir(),
File => entry.file_type().is_file(),
}),
_ => true,
};
let name_filter = |entry: &DirEntry| match &config.names {
Some(names) => names
.iter()
.any(|re| re.is_match(&entry.file_name().to_string_lossy())),
_ => true,
};
for dirname in &config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
let entries = WalkDir::new(dirname)
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.map(|entry| entry.path().display().to_string())
.collect::<Vec<String>>();
println!("{}", entries.join("\n"));
}
}
}
Ok(())
}
操作步骤如下:
1. 定义两个闭包
type_filter
和
name_filter
,分别用于过滤条目类型和文件名。
2. 遍历配置中的每个目录。
3. 检查目录是否可读取,若不可读取则打印错误信息并跳过。
4. 将
WalkDir
转换为迭代器,过滤掉错误值。
5. 使用
type_filter
和
name_filter
过滤条目。
6. 将每个
DirEntry
转换为字符串。
7. 收集所有符合条件的条目并打印。
3. 拓展功能建议
除了上述基本功能外,还可以对程序进行拓展,实现更多有用的功能:
-
控制搜索深度
:使用
WalkDir::min_depth
和
WalkDir::max_depth
控制搜索的最小和最大深度。
-
按文件大小查找
:使用
find
程序的
-size
语法,根据文件大小进行筛选。例如,
-size n[ckMGTP]
可查找大小符合条件的文件。
-
执行操作
:添加
-delete
选项,用于删除找到的条目;添加
-count
选项,统计找到的条目数量。
-
参考其他实现
:参考
fd
的源代码,学习其实现思路和优化方法。
4. 总结
通过这个文件查找程序,我们学习了以下技能:
- 使用
Arg::possible_values
限制参数值的范围,减少验证用户输入的时间。
- 使用正则表达式的
^
和
$
符号锚定模式的开头和结尾。
- 创建枚举类型来表示不同的可能性,比使用字符串更安全。
- 使用
WalkDir
递归搜索目录结构,并评估
DirEntry
值以查找文件、目录和链接。
- 学习如何使用迭代器链式调用多个操作,如
any
、
filter
、
map
和
filter_map
。
此外,还可以使用
csv
模块来读写分隔文本文件,这在处理分隔文本时非常有用。
Rust实现文件查找功能及拓展
5. 代码优化与安全性分析
在前面的代码实现中,我们已经完成了基本的文件查找功能,并对代码进行了一定的优化。下面我们来详细分析这些优化点以及代码的安全性。
5.1 闭包的使用
在重构后的
run
函数中,我们使用了闭包
type_filter
和
name_filter
来进行条目过滤。闭包的使用使得代码更加模块化,提高了代码的可读性和可维护性。具体来说:
-
type_filter
闭包根据配置中的条目类型对
DirEntry
进行过滤。它利用
iter().any()
方法检查是否有任何一个指定的条目类型与当前条目匹配。如果匹配,则返回
true
,否则返回
false
。
-
name_filter
闭包根据配置中的文件名正则表达式对
DirEntry
进行过滤。同样使用
iter().any()
方法检查是否有任何一个正则表达式与当前条目的文件名匹配。
以下是这两个闭包的代码:
let type_filter = |entry: &DirEntry| match &config.entry_types {
Some(types) => types.iter().any(|t| match t {
Link => entry.path_is_symlink(),
Dir => entry.file_type().is_dir(),
File => entry.file_type().is_file(),
}),
_ => true,
};
let name_filter = |entry: &DirEntry| match &config.names {
Some(names) => names
.iter()
.any(|re| re.is_match(&entry.file_name().to_string_lossy())),
_ => true,
};
5.2 迭代器链式调用
通过迭代器的链式调用,如
filter_map
、
filter
和
map
,我们可以将多个操作组合在一起,使得代码更加简洁高效。具体操作步骤如下:
1.
WalkDir::new(dirname).into_iter()
:将
WalkDir
对象转换为迭代器。
2.
.filter_map(|e| e.ok())
:过滤掉迭代器中的错误值,只保留
Ok
类型的值。
3.
.filter(type_filter)
:使用
type_filter
闭包过滤条目类型。
4.
.filter(name_filter)
:使用
name_filter
闭包过滤文件名。
5.
.map(|entry| entry.path().display().to_string())
:将每个
DirEntry
转换为其路径的字符串表示。
6.
.collect::<Vec<String>>()
:将所有符合条件的条目收集到一个
Vec<String>
中。
以下是完整的迭代器链式调用代码:
let entries = WalkDir::new(dirname)
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.map(|entry| entry.path().display().to_string())
.collect::<Vec<String>>();
5.3 代码安全性
在代码中,我们使用了枚举类型
EntryType
来表示不同的条目类型,这提高了代码的安全性。与使用字符串相比,枚举类型可以让编译器在编译时检查所有可能的情况,避免了运行时错误。例如,当我们在
type_filter
闭包中使用
match
语句时,如果遗漏了某个
EntryType
的匹配分支,编译器会报错,提示我们添加相应的分支。
以下是一个遗漏匹配分支导致的编译错误示例:
let type_filter = |entry: &DirEntry| match &config.entry_types {
Some(types) => types.iter().any(|t| match t {
Link => entry.path_is_symlink(),
Dir => entry.file_type().is_dir(),
//File => entry.file_type().is_file(),
}),
_ => true,
};
编译器会报错:
error[E0004]: non-exhaustive patterns: `&File` not covered
--> src/lib.rs:95:51
|
10 | / enum EntryType {
11 | | Dir,
12 | | File,
| | ---- not covered
13 | | Link,
14 | | }
| |_- `EntryType` defined here
...
95 | Some(types) => types.iter().any(|t| match t {
| ^ pattern `&File`
| not covered
|
= help: ensure that all possible cases are being handled, possibly by
adding wildcards or more match arms
= note: the matched value is of type `&EntryType`
6. 拓展功能实现思路
在前面我们提出了一些拓展功能的建议,下面我们来详细探讨这些功能的实现思路。
6.1 控制搜索深度
要控制搜索的最小和最大深度,可以使用
WalkDir
的
min_depth
和
max_depth
方法。具体实现步骤如下:
1. 在
get_args
函数中添加新的参数,用于指定最小和最大深度。
pub fn get_args() -> MyResult<Config> {
let matches = App::new("findr")
// 其他参数...
.arg(
Arg::with_name("min_depth")
.value_name("MIN_DEPTH")
.help("Minimum search depth")
.short("min")
.long("min-depth")
.takes_value(true),
)
.arg(
Arg::with_name("max_depth")
.value_name("MAX_DEPTH")
.help("Maximum search depth")
.short("max")
.long("max-depth")
.takes_value(true),
)
.get_matches();
let min_depth = matches.value_of("min_depth").and_then(|s| s.parse().ok());
let max_depth = matches.value_of("max_depth").and_then(|s| s.parse().ok());
// 其他处理...
Ok(Config {
// 其他配置...
min_depth,
max_depth,
})
}
-
在
run函数中使用这些参数来设置WalkDir的搜索深度。
pub fn run(config: Config) -> MyResult<()> {
for dirname in &config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
let mut walkdir = WalkDir::new(dirname);
if let Some(min) = config.min_depth {
walkdir = walkdir.min_depth(min);
}
if let Some(max) = config.max_depth {
walkdir = walkdir.max_depth(max);
}
let entries = walkdir
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.map(|entry| entry.path().display().to_string())
.collect::<Vec<String>>();
println!("{}", entries.join("\n"));
}
}
}
Ok(())
}
6.2 按文件大小查找
要按文件大小查找文件,可以在
run
函数中添加文件大小过滤逻辑。具体实现步骤如下:
1. 在
get_args
函数中添加新的参数,用于指定文件大小条件。
pub fn get_args() -> MyResult<Config> {
let matches = App::new("findr")
// 其他参数...
.arg(
Arg::with_name("size")
.value_name("SIZE")
.help("File size condition (e.g., n[ckMGTP])")
.short("s")
.long("size")
.takes_value(true),
)
.get_matches();
let size_condition = matches.value_of("size");
// 其他处理...
Ok(Config {
// 其他配置...
size_condition,
})
}
-
在
run函数中解析文件大小条件,并根据条件过滤文件。
pub fn run(config: Config) -> MyResult<()> {
let size_filter = |entry: &DirEntry| {
if let Some(condition) = &config.size_condition {
// 解析文件大小条件
let (size, unit) = parse_size_condition(condition);
let file_size = entry.metadata()?.len();
// 根据条件判断是否符合要求
match unit {
'c' => file_size == size,
'k' => file_size == size * 1024,
'M' => file_size == size * 1024 * 1024,
'G' => file_size == size * 1024 * 1024 * 1024,
'T' => file_size == size * 1024 * 1024 * 1024 * 1024,
'P' => file_size == size * 1024 * 1024 * 1024 * 1024 * 1024,
_ => false,
}
} else {
true
}
};
for dirname in &config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
let entries = WalkDir::new(dirname)
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.filter(size_filter)
.map(|entry| entry.path().display().to_string())
.collect::<Vec<String>>();
println!("{}", entries.join("\n"));
}
}
}
Ok(())
}
fn parse_size_condition(condition: &str) -> (u64, char) {
let mut chars = condition.chars();
let mut size_str = String::new();
let mut unit = ' ';
for c in chars {
if c.is_digit(10) {
size_str.push(c);
} else {
unit = c;
break;
}
}
let size = size_str.parse::<u64>().unwrap_or(0);
(size, unit)
}
6.3 执行操作
要实现
-delete
和
-count
选项,可以在
get_args
函数中添加相应的参数,并在
run
函数中处理这些参数。
- -delete 选项 :
pub fn get_args() -> MyResult<Config> {
let matches = App::new("findr")
// 其他参数...
.arg(
Arg::with_name("delete")
.help("Delete matching entries")
.short("d")
.long("delete"),
)
.get_matches();
let delete = matches.is_present("delete");
// 其他处理...
Ok(Config {
// 其他配置...
delete,
})
}
pub fn run(config: Config) -> MyResult<()> {
for dirname in &config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
let entries = WalkDir::new(dirname)
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.collect::<Vec<DirEntry>>();
for entry in entries {
if config.delete {
if entry.file_type().is_file() {
fs::remove_file(entry.path())?;
} else if entry.file_type().is_dir() {
fs::remove_dir_all(entry.path())?;
}
} else {
println!("{}", entry.path().display());
}
}
}
}
}
Ok(())
}
- -count 选项 :
pub fn get_args() -> MyResult<Config> {
let matches = App::new("findr")
// 其他参数...
.arg(
Arg::with_name("count")
.help("Count matching entries")
.short("c")
.long("count"),
)
.get_matches();
let count = matches.is_present("count");
// 其他处理...
Ok(Config {
// 其他配置...
count,
})
}
pub fn run(config: Config) -> MyResult<()> {
let mut total_count = 0;
for dirname in &config.dirs {
match fs::read_dir(&dirname) {
Err(e) => eprintln!("{}: {}", dirname, e),
_ => {
let entries = WalkDir::new(dirname)
.into_iter()
.filter_map(|e| e.ok())
.filter(type_filter)
.filter(name_filter)
.collect::<Vec<DirEntry>>();
total_count += entries.len();
if !config.count {
for entry in entries {
println!("{}", entry.path().display());
}
}
}
}
}
if config.count {
println!("Total count: {}", total_count);
}
Ok(())
}
7. 总结与展望
通过这个文件查找程序,我们不仅实现了基本的文件查找功能,还对代码进行了优化和拓展。在实现过程中,我们学习了如何使用 Rust 的各种特性,如枚举类型、闭包、迭代器等,提高了代码的可读性、可维护性和安全性。
未来,我们可以进一步完善这个程序,例如添加更多的过滤条件、优化性能、改进用户界面等。同时,我们也可以将这些技术应用到其他项目中,提高开发效率和代码质量。
总之,Rust 是一门强大的编程语言,通过不断学习和实践,我们可以充分发挥其优势,开发出更加优秀的软件。
超级会员免费看
31

被折叠的 条评论
为什么被折叠?



