1. 文件读写
1.1 读取文件的字符串行
向文件写入三行信息,然后使用 BufRead::lines
创建的迭代器 Lines
读取文件,一次读回一行。File
模块实现了提供 BufReader
结构体的 Read
trait。File::create
打开文件 File 进行写入,File::open
则进行读取。
use std::fs::File; use std::io::{BufRead, BufReader, Error, Write};
fn main() -> Result<(), Error> { let path = "lines.txt";
let mut output = File::create(path)?; write!(output, "Rust\n💖\n呵呵")?;
let input = File::open(path)?; let buffered = BufReader::new(input);
for line in buffered.lines() { println!("{}", line?); }
Ok(()) }
|
1.2 避免读取写入同一文件
需要安装same-file
库,可通过cargo add same-file
命令安装
[dependencies] same-file = "1.0.6"
|
对文件使用 same_file::Handle
结构体,可以测试文件句柄是否等同。在本实例中,将对要读取和写入的文件句柄进行相等性测试。
use same_file::Handle; use std::fs::File; use std::io::{BufRead, BufReader, Error, ErrorKind}; use std::path::Path;
fn main() -> Result<(), Error> { let path_to_read = Path::new("new.txt");
let stdout_handle = Handle::stdout()?; let handle = Handle::from_path(path_to_read)?;
if stdout_handle == handle { return Err(Error::new(ErrorKind::Other, "您正在读取和写入同一个文件")); } else { let file = File::open(&path_to_read)?; let file = BufReader::new(file); for (num, line) in file.lines().enumerate() { println!("{} : {}", num, line?.to_uppercase()); } }
Ok(()) }
|
Error: Custom { kind: Other, error: "您正在读取和写入同一个文件" }
|
1.3 使用内存映射随机访问文件
需要安装memmap
库,可通过cargo add memmap
命令安装
[dependencies] memmap = "0.7.0"
|
使用 memmap
创建文件的内存映射,并模拟文件的一些非序列读取。使用内存映射意味着您仅需索引一个切片,而不是使用 seek
方法来导航整个文件。Mmap::map
函数假定内存映射后的文件没有被另一个进程同时更改,否则会出现竞态条件。
use memmap::Mmap; use std::fs::File; use std::io::{Write, Error};
fn main() -> Result<(), Error> { write!(File::create("content.txt")?, "My hovercraft is full of eels!")?;
let file = File::open("content.txt")?; let map = unsafe { Mmap::map(&file)? };
let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29]; assert_eq!(&map[3..13], b"hovercraft"); let random_bytes: Vec<u8> = random_indexes.iter() .map(|&idx| map[idx]) .collect(); assert_eq!(&random_bytes[..], b"My loaf!"); Ok(()) }
|
2 目录遍历
2.1 过去 24 小时内修改过的文件名
需要安装error_chain
库,可通过cargo add error_chain
命令安装
[dependencies] error-chain = "0.12.4"
|
通过调用 env::current_dir
获取当前工作目录,然后通过 fs::read_dir
读取目录中的每个条目,通过 DirEntry::path
提取条目路径,以及通过通过 fs::Metadata
获取条目元数据。Metadata::modified
返回条目自上次更改以来的运行时间 SystemTime::elapsed。Duration::as_secs
将时间转换为秒,并与 24 小时(24 60 60 秒)进行比较。Metadata::is_file
用于筛选出目录。
use error_chain::error_chain;
use std::{env, fs};
error_chain! { foreign_links { Io(std::io::Error); SystemTimeError(std::time::SystemTimeError); } }
fn main() -> Result<()> { let current_dir = env::current_dir()?; println!("在过去 24 小时内修改的条目{:?}:", current_dir);
for entry in fs::read_dir(current_dir)? { let entry = entry?; let path = entry.path();
let metadata = fs::metadata(&path)?; let last_modified = metadata.modified()?.elapsed()?.as_secs();
if last_modified < 24 * 3600 && metadata.is_file() { println!( "最后修改:{:?} 秒,只读:{:?},大小:{:?} 字节,文件名:{:?}", last_modified, metadata.permissions().readonly(), metadata.len(), path.file_name().ok_or("没有文件名")? ); } }
Ok(()) }
|
在过去 24 小时内修改的条目"/root/rustcookbook/filetest": 最后修改:118 秒,只读:false,大小:237 字节,文件名:"Cargo.toml" 最后修改:107 秒,只读:false,大小:4336 字节,文件名:"Cargo.lock"
|
2.2 查找给定路径的循环
使用 same_file::is_same_file
检测给定路径的循环。例如,可以通过软连接(符号链接)在 Unix
系统上创建循环:
mkdir -p /tmp/foo/bar/baz ln -s /tmp/foo/ /tmp/foo/bar/baz/qux
|
use same_file::is_same_file; use std::io; use std::path::{Path, PathBuf};
fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> { let path = path.as_ref(); let mut path_buf = path.to_path_buf(); while path_buf.pop() { if is_same_file(&path_buf, path)? { return Ok(Some((path_buf, path.to_path_buf()))); } else if let Some(looped_paths) = contains_loop(&path_buf)? { return Ok(Some(looped_paths)); } } return Ok(None); }
fn main() { assert_eq!( contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(), Some(( PathBuf::from("/tmp/foo"), PathBuf::from("/tmp/foo/bar/baz/qux") )) ); }
|
2.3 递归查找重名文件
需要安装walkdir
库,可通过cargo add walkdir
命令安装
[dependencies] walkdir = "2.3.3"
|
在当前目录中递归查找重复的文件名,只打印一次。
use std::collections::HashMap; use walkdir::WalkDir;
fn main() { let mut filenames = HashMap::new();
for entry in WalkDir::new(".") .into_iter() .filter_map(Result::ok) .filter(|e| !e.file_type().is_dir()) { let f_name = String::from(entry.file_name().to_string_lossy()); let counter = filenames.entry(f_name.clone()).or_insert(0); *counter += 1;
if *counter == 2 { println!("{}", f_name); } } }
|
- 运行
cargo run
输出(基本上是target
目录下的文件)
invoked.timestamp root-output output stderr ... dep-lib-adler
|
2.4 使用给定断言递归查找所有文件
在当前目录中查找最近一天内修改的 JSON
文件。使用 follow_links
确保软链接(符号链接)像普通目录和文件一样被按照当前查找规则执行。
use error_chain::error_chain;
use walkdir::WalkDir;
error_chain! { foreign_links { WalkDir(walkdir::Error); Io(std::io::Error); SystemTime(std::time::SystemTimeError); } }
fn main() -> Result<()> { for entry in WalkDir::new(".") .follow_links(true) .into_iter() .filter_map(|e| e.ok()) { let f_name = entry.file_name().to_string_lossy(); let sec = entry.metadata()?.modified()?;
if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 { println!("{}", f_name); } }
Ok(()) }
|
- 运行
cargo run
输出(基本上是target
目录下的文件)
.rustc_info.json lib-gimli.json ... lib-adler.json bin-filetest.json
|
2.5 跳过隐藏文件遍历目录
递归下行到子目录的过程中,使用 filter_entry
对目录中的条目传递 is_not_hidden
断言,从而跳过隐藏的文件和目录。Iterator::filter
可应用到要检索的任何目录 WalkDir::DirEntry
,即使父目录是隐藏目录。根目录 “.” 的检索结果,通过在断言 is_not_hidden
中使用 WalkDir::depth
参数生成。
use walkdir::{DirEntry, WalkDir};
fn is_not_hidden(entry: &DirEntry) -> bool { entry .file_name() .to_str() .map(|s| entry.depth() == 0 || !s.starts_with(".")) .unwrap_or(false) }
fn main() { WalkDir::new(".") .into_iter() .filter_entry(|e| is_not_hidden(e)) .filter_map(|v| v.ok()) .for_each(|x| println!("{}", x.path().display())); }
|
- 运行
cargo run
输出(基本上是target
目录下的文件)
./Cargo.toml ./src ./src/main.rs ./target ./target/debug ./target/debug/build ... ./Cargo.lock
|
2.6 在给定深度的目录,递归计算文件大小
通过WalkDir::min_depth
和 WalkDir::max_depth
方法,可以灵活设置目录的递归深度。下面的实例计算了 3 层子文件夹深度的所有文件的大小总和,计算中忽略根文件夹中的文件。
use walkdir::WalkDir;
fn main() { let total_size = WalkDir::new(".") .min_depth(1) .max_depth(3) .into_iter() .filter_map(|entry| entry.ok()) .filter_map(|entry| entry.metadata().ok()) .filter(|metadata| metadata.is_file()) .fold(0, |acc, m| acc + m.len());
println!("总大小: {} bytes.", total_size); }
|
2.7 递归查找所有 png
文件
需要安装glob"
库,可通过cargo add glob
命令安装
[dependencies] glob = "0.3.1"
|
递归地查找当前目录中的所有 PNG
文件。在本实例中,**
模式用于匹配当前目录及其所有子目录。在路径任意部分使用 **
模式,例如,/media/**/*.png
匹配 media
及其子目录中的所有 PNG
文件。
use error_chain::error_chain;
use glob::glob;
error_chain! { foreign_links { Glob(glob::GlobError); Pattern(glob::PatternError); } }
fn main() -> Result<()> { for entry in glob("**/*.png")? { println!("{}", entry?.display()); }
Ok(()) }
|
2.8 忽略文件名大小写,使用给定模式查找所有文件
在当前目录中查找与正则表达模式 img_[0-9]*.png
匹配的所有图像文件。一个自定义 MatchOptions
结构体被传递给 glob_with
函数,使全局命令模式下不区分大小写,同时保持其他选项的默认值 Default
,注:原教程查看/media/
目录下的图片。
use error_chain::error_chain; use glob::{glob_with, MatchOptions};
error_chain! { foreign_links { Glob(glob::GlobError); Pattern(glob::PatternError); } }
fn main() -> Result<()> { let options = MatchOptions { case_sensitive: false, ..Default::default() };
for entry in glob_with("./**/img_[0-9]*.png", options)? { println!("{}", entry?.display()); }
Ok(()) }
|