13、Rust实现文件查找功能及拓展

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,
    })
}
  1. 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,
    })
}
  1. 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 是一门强大的编程语言,通过不断学习和实践,我们可以充分发挥其优势,开发出更加优秀的软件。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制问题,并提供完整的Matlab代码实现。文章结合数据驱动方法与Koopman算子理论,利用递归神经网络(RNN)对非线性系统进行建模与线性化处理,从而提升纳米级定位系统的精度与动态响应性能。该方法通过提取系统隐含动态特征,构建近似线性模型,便于后续模型预测控制(MPC)的设计与优化,适用于高精度自动化控制场景。文中还展示了相关实验验证与仿真结果,证明了该方法的有效性和先进性。; 适合人群:具备一定控制理论基础和Matlab编程能力,从事精密控制、智能制造、自动化或相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能控制设计;②为非线性系统建模与线性化提供一种结合深度学习与现代控制理论的新思路;③帮助读者掌握Koopman算子、RNN建模与模型预测控制的综合应用。; 阅读建议:建议读者结合提供的Matlab代码逐段理解算法实现流程,重点关注数据预处理、RNN结构设计、Koopman观测矩阵构建及MPC控制器集成等关键环节,并可通过更换实际系统数据进行迁移验证,深化对方法泛化能力的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值