🐡
Rustの基本的なFile,Dir操作メモ
Rustの基本的なファイル・ディレクトリ操作について調べたので備忘として残しておきます。
パス操作
サンプルコード
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::ffi::{OsStr, OsString};
fn main() -> Result<()> {
// get &Path
let dir: &Path = Path::new("./rsc/");
let file1: &Path = Path::new("./rsc/file_foo.txt");
// get PathBuf
let mut file2: PathBuf = PathBuf::from(dir);
file2.push("file_bar");
file2.set_extension("txt");
assert_eq!(file2, dir.join("file_bar.txt"));
// convert &Path from/to PathBuf
let _pathbuf: PathBuf = file1.to_path_buf();
let _path: &Path = file2.as_path();
let _path: &Path = &*file2;
// check path status
assert!(dir.try_exists()?); // error if not accessable
assert!(dir.is_dir());
assert!(file1.is_file());
assert!(!dir.is_absolute());
assert!(dir.is_relative());
// get path component
assert_eq!(file1.parent().unwrap(), dir);
assert_eq!(file1.extension().unwrap(), OsStr::new("txt"));
assert_eq!(file1.file_name().unwrap(), OsStr::new("file_foo.txt"));
assert_eq!(dir.file_name().unwrap(), OsStr::new("rsc"));
// get dir contents
for result in dir.read_dir().context("read dir error")? {
if let Ok(entry) = result {
let _pathbuf: PathBuf = entry.path();
let name: OsString = entry.file_name();
println!("{}", name.to_str().unwrap());
}
}
Ok(())
}
メモ
-
Path
はstr
のようなもの-
&Path
は&str
、PathBuf
はString
に相当する -
&OsStr
とOsString
も似たようなもの
-
- ディレクトリの内容取得はパスからもできる
-
std::fs::read_dir
も同じことができる
-
-
try_exists
等はTOCTOUが発生しうることを念頭に置いておく
ファイル読み書き
サンプルコード
use anyhow::{Context, Result};
use std::fs::File;
use std::path::{Path, PathBuf};
fn main() -> Result<()> {
let file1: &Path = Path::new("./rsc/file_foo.txt");
let file2: &Path = Path::new("./rsc/file_bar.txt");
let file3: &Path = Path::new("./rsc/file_baz.txt");
let (reader, writer) = get_text_rw(file1, WriteMode::Replace);
let body1 = ["abc", "def", "123"].iter().map(|&x| x.to_string()).collect::<Vec<String>>();
// efficient file io
writer.write_all_as_lines(body1)?;
println!("{}", reader.read_all_as_lines()?); // abc\ndef\n123
let (reader, writer) = get_text_rw(file2, WriteMode::Append);
let body2 = "append_bar".to_string();
// simple file io
writer.write_all_as_whole(body2)?;
println!("{}", reader.read_all_as_whole()?); // ... append_bar
let (reader, writer) = get_text_rw(file3, WriteMode::AsNew);
let body3 = ["t", "x", "t"].iter().map(|&x| x.to_string()).collect::<Vec<String>>();
writer.write_all_as_lines(body3)?; // error if file3 exists
println!("{}", reader.read_all_as_lines()?); // t\nx\nt
Ok(())
}
fn get_text_rw(path: &Path, mode: WriteMode) -> (TextFileReader, TextFileWriter) {
(TextFileReader::new(path), TextFileWriter::new(path, mode))
}
struct TextFileReader {
path: PathBuf,
}
impl TextFileReader {
fn new(path: &Path) -> Self {
Self { path: path.to_path_buf() }
}
fn open_file(&self) -> Result<File> {
File::open(self.path.as_path()).context("cannot open path as read mode") // read only
}
fn read_all_as_lines(&self) -> Result<String> {
use std::io::{BufRead, BufReader};
let buff_reader: BufReader<File> = BufReader::new(self.open_file()?);
let mut lines = Vec::new();
for line in buff_reader.lines() {
// if process here, can save memory
lines.push(line.context("read error")?);
}
Ok(lines.join("\n"))
}
fn read_all_as_whole(&self) -> Result<String> {
use std::io::Read;
let mut body = String::new();
self.open_file()?.read_to_string(&mut body).context("read error")?;
Ok(body)
}
}
enum WriteMode {
Replace,
AsNew,
Append,
}
struct TextFileWriter {
path: PathBuf,
mode: WriteMode,
}
impl TextFileWriter {
fn new(path: &Path, mode: WriteMode) -> Self {
Self { path: path.to_path_buf(), mode }
}
fn open_file(&self) -> Result<File> {
use std::fs::OpenOptions;
match self.mode {
WriteMode::Replace => File::create(self.path.as_path()).context("cannot open path as write mode"), // write only (write,create,truncate)
WriteMode::AsNew => OpenOptions::new().create_new(true).write(true).open(self.path.as_path()).context("cannot open path as new file"),
WriteMode::Append => OpenOptions::new().append(true).create(true).open(self.path.as_path()).context("cannot open path as append mode"),
}
}
fn write_all_as_lines(&self, body: Vec<String>) -> Result<()> {
use std::io::{BufWriter, Write};
let mut buff_writer: BufWriter<File> = BufWriter::new(self.open_file()?);
for line in body.into_iter().map(|s| s + "\n") {
buff_writer.write(line.as_bytes()).context("write error")?;
}
buff_writer.flush().context("flush error")?;
Ok(())
}
fn write_all_as_whole(&self, body: String) -> Result<()> {
use std::io::Write;
self.open_file()?.write_all((body + "\n").as_bytes()).context("write error")?;
Ok(())
}
}
メモ
- ファイル全体を一度に読み込むとメモリ消費が激しいため
BufReader
の使用が好ましい -
BufReader
,BufWriter
は同ファイルに対する小さい値の繰り返し読み,書きで性能向上しやすい -
BufWriter
は明示的に最後にflush
するのが望ましい-
BufWriter
ドロップ時に自動flush
されるが、ドロップ時のエラー発生は無視され書き込まれない
-
- ファイルに追記するためには
std::fs::OpenOptions
を用いる必要がある
ファイル,ディレクトリ操作
サンプルコード
use anyhow::Result;
use std::path::Path;
use std::fs::*;
fn main() -> Result<()> {
let file3: &Path = Path::new("./rsc/file_baz.txt");
create_dir(Path::new("./rsc/foo/"))?;
create_dir_all(Path::new("./rsc/foo/bar/baz"))?; // create dir with all parent dirs
copy(file3, Path::new("./rsc/foo/bar/baz/qux.txt"))?;
rename(Path::new("./rsc/foo/bar/baz/qux.txt"), Path::new("./rsc/foo/bar/baz/quux.txt"))?;
remove_dir_all(Path::new("./rsc/foo/"))?;
remove_file(file3)?;
Ok(())
}
メモ
- symlink向けの機能もある(詳細は公式参照)
Discussion