查找与动态模式匹配的文件
Find Files that Match a Dynamic Pattern
我希望能够解析目录中的所有文件,以找到具有与用户提供的模式匹配的最大时间戳的文件。
即如果用户运行
$ search /foo/bar/baz.txt
并且目录/foo/bar/
包含文件baz.001.txt
、baz.002.txt
和baz.003.txt
,那么结果应该是baz.003.txt
目前我正在构建 PathBuf
。
用它来构建 Regex
。
然后在目录中查找与表达式匹配的所有文件。
但是感觉对于一个相对简单的问题来说,这是一个很大的工作量。
fn find(foo: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut files = vec![];
let mut path = PathBuf::from(foo);
let base = path.parent().unwrap().to_str().unwrap();
let file_name = path.file_stem().unwrap().to_str().unwrap();
let extension = path.extension().unwrap().to_str().unwrap();
let pattern = format!("{}\.\d{{3}}\.{}", file_name, extension);
let expression = Regex::new(&pattern).unwrap();
let objects: Vec<String> = fs::read_dir(&base)
.unwrap()
.map(|entry| {
entry
.unwrap()
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned()
})
.collect();
for object in objects.iter() {
if expression.is_match(object) {
files.push(String::from(object));
}
}
Ok(files)
}
有没有更简单的方法来获取文件路径、生成模式并找到所有匹配的文件?
Rust 并不是真正适用于快速而肮脏的解决方案的语言。相反,它强烈地鼓励优雅的解决方案,所有极端情况都得到妥善处理。这通常不会导致极短的解决方案,但您可以避免过多依赖外部包装箱的样板文件,这些包装箱会包含大量代码。假设您还没有出现“library-wide”错误,我会这样做。
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let path = PathBuf::from(foo);
let base = path
.parent()
.ok_or(FindError::InvalidBaseFile)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_name = path
.file_stem()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_extension = path
.extension()
.ok_or(FindError::NoFileExtension)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let pattern = format!(r"{}\.\d{{3}}\.{}", file_name, file_extension);
let expression = Regex::new(&pattern)?;
Ok(
fs::read_dir(&base)?
.map(|entry| Ok(
entry?
.path()
.file_name()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?
.to_string()
))
.collect::<Result<Vec<_>, FindError>>()?
.into_iter()
.filter(|file_name| expression.is_match(&file_name))
.collect()
)
}
FindError
的简单定义可以通过 thiserror
箱子实现:
use thiserror::Error;
#[derive(Error, Debug)]
enum FindError {
#[error(transparent)]
RegexError(#[from] regex::Error),
#[error("File name has no extension")]
NoFileExtension,
#[error("Not a valid file name")]
InvalidFileName,
#[error("No valid base file")]
InvalidBaseFile,
#[error("An OS string is not valid utf-8")]
OsStringNotUtf8,
#[error(transparent)]
IoError(#[from] std::io::Error),
}
编辑
正如@Masklinn 所指出的,您可以轻松检索文件的主干和扩展名。它会导致 less-well 处理错误(并且一些特殊情况,例如没有扩展名的隐藏文件处理不当),但整体代码不那么冗长。供您根据需要选择。
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let (file_name, file_extension) = foo
.rsplit_one('.')
.ok_or(FindError::NoExtension)?;
... // the rest is unchanged
}
您可能也需要适应 FindError
。你也可以摆脱 ok_or
的情况,如果你真的不关心它,只需用 .unwrap_or((foo, ""))
替换它(但是这会产生令人惊讶的结果......)。
我希望能够解析目录中的所有文件,以找到具有与用户提供的模式匹配的最大时间戳的文件。
即如果用户运行
$ search /foo/bar/baz.txt
并且目录/foo/bar/
包含文件baz.001.txt
、baz.002.txt
和baz.003.txt
,那么结果应该是baz.003.txt
目前我正在构建 PathBuf
。
用它来构建 Regex
。
然后在目录中查找与表达式匹配的所有文件。
但是感觉对于一个相对简单的问题来说,这是一个很大的工作量。
fn find(foo: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut files = vec![];
let mut path = PathBuf::from(foo);
let base = path.parent().unwrap().to_str().unwrap();
let file_name = path.file_stem().unwrap().to_str().unwrap();
let extension = path.extension().unwrap().to_str().unwrap();
let pattern = format!("{}\.\d{{3}}\.{}", file_name, extension);
let expression = Regex::new(&pattern).unwrap();
let objects: Vec<String> = fs::read_dir(&base)
.unwrap()
.map(|entry| {
entry
.unwrap()
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned()
})
.collect();
for object in objects.iter() {
if expression.is_match(object) {
files.push(String::from(object));
}
}
Ok(files)
}
有没有更简单的方法来获取文件路径、生成模式并找到所有匹配的文件?
Rust 并不是真正适用于快速而肮脏的解决方案的语言。相反,它强烈地鼓励优雅的解决方案,所有极端情况都得到妥善处理。这通常不会导致极短的解决方案,但您可以避免过多依赖外部包装箱的样板文件,这些包装箱会包含大量代码。假设您还没有出现“library-wide”错误,我会这样做。
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let path = PathBuf::from(foo);
let base = path
.parent()
.ok_or(FindError::InvalidBaseFile)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_name = path
.file_stem()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_extension = path
.extension()
.ok_or(FindError::NoFileExtension)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let pattern = format!(r"{}\.\d{{3}}\.{}", file_name, file_extension);
let expression = Regex::new(&pattern)?;
Ok(
fs::read_dir(&base)?
.map(|entry| Ok(
entry?
.path()
.file_name()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?
.to_string()
))
.collect::<Result<Vec<_>, FindError>>()?
.into_iter()
.filter(|file_name| expression.is_match(&file_name))
.collect()
)
}
FindError
的简单定义可以通过 thiserror
箱子实现:
use thiserror::Error;
#[derive(Error, Debug)]
enum FindError {
#[error(transparent)]
RegexError(#[from] regex::Error),
#[error("File name has no extension")]
NoFileExtension,
#[error("Not a valid file name")]
InvalidFileName,
#[error("No valid base file")]
InvalidBaseFile,
#[error("An OS string is not valid utf-8")]
OsStringNotUtf8,
#[error(transparent)]
IoError(#[from] std::io::Error),
}
编辑
正如@Masklinn 所指出的,您可以轻松检索文件的主干和扩展名。它会导致 less-well 处理错误(并且一些特殊情况,例如没有扩展名的隐藏文件处理不当),但整体代码不那么冗长。供您根据需要选择。
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let (file_name, file_extension) = foo
.rsplit_one('.')
.ok_or(FindError::NoExtension)?;
... // the rest is unchanged
}
您可能也需要适应 FindError
。你也可以摆脱 ok_or
的情况,如果你真的不关心它,只需用 .unwrap_or((foo, ""))
替换它(但是这会产生令人惊讶的结果......)。