Rustの基礎 - ディレクトリ
概要
ディレクトリに含まれるファイルの一覧を取得したり、ディレクトリ名を変更したりすることができる。
ディレクトリ操作には、標準ライブラリの std::fs モジュールを使用する。
ディレクトリの作成 / 削除
ディレクトリの作成
ディレクトリを新規作成する場合は、std::fs::create_dir メソッドを使用する。
create_dirメソッドは、引数に指定したパスが示すディレクトリを新規作成する。
このメソッドは、Result型を返すため、エラーハンドリングが必要である。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/movie";
fs::create_dir(path)?;
Ok(())
}
※注意
create_dirメソッドは、中間ディレクトリを作成することができない。
例えば、/home/hoge/img/backディレクトリを作成する場合、/home/hoge/imgディレクトリが存在していない時は、エラーが発生する。
また、新規作成するディレクトリが既に存在する場合も、エラーが発生する。
中間ディレクトリも同時に作成する
中間ディレクトリも同時に新規作成する場合、std::fs::create_dir_all メソッドを使用する。
引数に指定したパスが示すディレクトリを新規作成する。
新規作成するディレクトリが既に存在する場合でも、エラーは発生しない。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/movie/back";
fs::create_dir_all(path)?;
Ok(())
}
以下の例では、./test/movie/backディレクトリを新規作成している。
./test/movie/backディレクトリが存在しない場合でも、中間ディレクトリである./test/movieディレクトリを作成後、指定のパスのディレクトリが作成される。
use std::fs;
fn main() {
let path = "./test/movie/back";
match fs::create_dir_all(path) {
Ok(_) => println!("ディレクトリを作成しました: {}", path),
Err(e) => eprintln!("エラー: {}", e),
}
}
ディレクトリの削除
ディレクトリを削除する場合は、std::fs::remove_dir メソッドを使用する。
remove_dirメソッドは、引数に指定したパスが示すディレクトリを削除する。
ただし、ディレクトリを削除する場合は、ディレクトリの中身が空である必要があることに注意する。
削除するディレクトリが空ではない場合、エラーが発生する。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/doc";
fs::remove_dir(path)?;
Ok(())
}
存在しないディレクトリを削除する場合、エラーが発生する。
use std::fs;
fn main() {
let path = "./test/doc";
match fs::remove_dir(path) {
Ok(_) => println!("ディレクトリを削除しました: {}", path),
Err(e) => eprintln!("エラー: {}", e),
}
}
ディレクトリとディレクトリの中身の一括削除
ディレクトリとディレクトリ内に存在するファイルを一括して削除する場合、std::fs::remove_dir_all メソッドを使用する。
引数に指定したパスが示すディレクトリを削除する。
ディレクトリ内にファイルやディレクトリが含まれている場合は、一括削除する。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/movie";
fs::remove_dir_all(path)?;
Ok(())
}
以下の例では、エラーハンドリングを行っている。
use std::fs;
fn main() {
let path = "./test/movie";
match fs::remove_dir_all(path) {
Ok(_) => println!("ディレクトリとその内容を削除しました: {}", path),
Err(e) => eprintln!("エラー: {}", e),
}
}
ディレクトリの存在確認
指定したパスが示すディレクトリが存在するかどうかを確認する場合、std::path::Path の exists メソッドを使用する。
パスが存在する場合は、trueを返す。
また、existsメソッドは、引数に指定したパスがファイルまたはディレクトリであっても存在する場合は、trueを返すことに注意する。
use std::path::Path;
fn main() {
let path1 = Path::new("./test/address.txt");
if path1.exists() {
println!("{}は存在します", path1.display());
}
else {
println!("{}は存在しません", path1.display());
}
let path2 = Path::new("./test/user.txt");
if path2.exists() {
println!("{}は存在します", path2.display());
}
else {
println!("{}は存在しません", path2.display());
}
}
ディレクトリであることを確認するには、is_dir メソッドを併用する。
use std::path::Path;
fn main() {
let path = Path::new("./test");
if path.exists() && path.is_dir() {
println!("{}はディレクトリとして存在します", path.display());
}
}
ディレクトリ名の変更
ディレクトリ名を変更する場合は、std::fs::rename メソッドを使用する。
第1引数に指定したパスが示すディレクトリ名を、第2引数に指定したパスが示すディレクトリ名に変更する。
ディレクトリ名を変更する場合、変更前と変更後でディレクトリの親ディレクトリが異なっていても問題ない。
ただし、ディレクトリ名を変更する時、変更後の親ディレクトリが存在しない場合、エラーが発生する。
use std::fs;
fn main() -> std::io::Result<()> {
let oldpath = "./test/book";
let newpath = "./test/memo";
fs::rename(oldpath, newpath)?;
Ok(())
}
以下の例では、エラーハンドリングを行っている。
use std::fs;
fn main() {
let oldpath = "./test/book";
let newpath = "./test/memo";
match fs::rename(oldpath, newpath) {
Ok(_) => println!("ディレクトリ名を変更しました: {} -> {}", oldpath, newpath),
Err(e) => eprintln!("エラー: {}", e),
}
}
変更後のパスが既に存在する場合の動作は、プラットフォームに依存する。
Windows環境の場合、変更後のパスが既に存在する場合、エラーが発生することがある。
use std::fs;
fn main() {
let oldpath = "./test/book";
let newpath = "./back/memo";
// 既にmemoディレクトリが存在する場合は、エラーが発生する可能性がある
match fs::rename(oldpath, newpath) {
Ok(_) => println!("ディレクトリ名を変更しました"),
Err(e) => eprintln!("エラー: {}", e),
}
}
条件に一致するディレクトリの一覧の取得
globクレートを使用したパターンマッチング
指定した条件に一致するファイルやディレクトリの一覧を取得する場合は、glob クレートを使用する。
globクレートは外部クレートであるため、Cargo.tomlファイルに以下の設定を追加する必要がある。
[dependencies]
glob = "0.3"
globメソッドは、パスパターンとマッチするファイルおよびディレクトリを、イテレータとして取得する。
この時、パスには以下の特殊文字を指定することができる。
- *
- 0文字以上の任意の文字
- ?
- 1文字の任意の文字
- [abc]
- 括弧の中のいずれかの文字
*は、0文字以上の任意の文字とマッチする。
例えば、*.txtと指定する場合、a.txtやmemo.txt等の"0文字以上の任意の文字列" + ".txt"に一致するファイルおよびディレクトリの一覧を取得する。
以下の例では、./testディレクトリ内の全てのファイルおよびディレクトリの一覧を取得している。
use glob::glob;
fn main() {
for entry in glob("./test/*").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test/img
./test/movie
./test/pen.jpg
以下の例では、bから始まるファイルおよびディレクトリの一覧を取得している。
use glob::glob;
fn main() {
for entry in glob("./test/b*").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test/back
./test/book.png
? は、1文字の任意の文字とマッチする。
例えば、?.txtと指定する場合、a.txtやc.txt等の"1文字の任意の文字" + ".txt"と一致するファイルやディレクトリの一覧を取得する。
この時、2文字以上のabc.txt等にはマッチしない。
以下の例では、3文字の任意の文字で始まり、末尾が.txtのファイルおよびディレクトリの一覧を取得している。
use glob::glob;
fn main() {
for entry in glob("./test/???.txt").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test/cup.txt
./test/pen.txt
[] は、括弧の中に記述した文字のいずれか1文字とマッチする。
例えば、199[789].txtと指定する場合、1997.txt、1998.txt、1999.txtと一致するファイルやディレクトリの一覧を取得する。
この時、1文字ではない19978.txt等にはマッチしない。
また、[3-6] や [a-e] 等のようにハイフンを記述することにより、文字の範囲を指定することができる。
[3-6]は[3456]と等価、[a-e]は[abcde]と等価である。
以下の例では、最初にaからeまでの文字で始まり、末尾が.txtのファイルおよびディレクトリの一覧を取得している。
use glob::glob;
fn main() {
println!("a-eで始まる.txtファイル:");
for entry in glob("./test/[a-e]*.txt").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
println!("\nc-zで始まる.txtファイル:");
for entry in glob("./test/[c-z]*.txt").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test/book.txt
./test/environment.txt
./test/flavor.txt
./test/pen.txt
再帰的にディレクトリの一覧の取得
パスの指定において、** を使用することにより、全てのファイルおよび0個以上のディレクトリとサブディレクトリにマッチする。
例えば、パスを./**/*.txtと指定する場合、a.txtやmemo.txt等の同階層のディレクトリにあるファイルの他に、
./doc/b.txtや./html/back/2020/report.txt等のサブディレクトリにあるファイルも対象となる。
以下の例では、サブディレクトリを再帰的に検索して条件に一致するファイルを取得している。
use glob::glob;
fn main() {
for entry in glob("./test/**/*").expect("パターンの読み込みに失敗") {
match entry {
Ok(path) => println!("{}", path.display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test/img
./test/movie
./test/pen.txt
./test/back
./test/back/2020.txt
ディレクトリに含まれるファイルとディレクトリの一覧の取得
ディレクトリの内容の取得
指定したディレクトリに含まれるファイルとディレクトリの一覧を取得する場合、std::fs::read_dir メソッドを使用する。
引数にディレクトリを指定する時、そのディレクトリに含まれるエントリ (ファイル または ディレクトリ) のイテレータを返す。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/";
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
println!("{}", entry.path().display());
}
Ok(())
}
// 出力例
./test/address.txt
./test/doc
./test/img
./test/name.txt
エントリから file_name メソッドを使用してファイル名のみを取得することもできる。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/";
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
println!("{}", entry.file_name().to_string_lossy());
}
Ok(())
}
ファイルとディレクトリの判別
指定したパスにおいて、ファイルまたはディレクトリを判別する場合、std::path::Path の is_file メソッド および is_dir メソッドを使用する。
is_fileメソッドは、パスが存在、かつ、ファイルの場合はtrueを返す。
is_dirメソッドは、パスが存在、かつ、ディレクトリの場合はtrueを返す。
use std::path::Path;
fn main() {
let path = Path::new("./test/");
println!("is_file: {}", path.is_file());
println!("is_dir: {}", path.is_dir());
let path2 = Path::new("./test/address.txt");
println!("is_file: {}", path2.is_file());
println!("is_dir: {}", path2.is_dir());
}
// 出力
is_file: false
is_dir: true
is_file: true
is_dir: false
以下の例では、./testディレクトリに含まれるファイルとディレクトリの一覧を取得して、ファイルの場合は[F] + ファイル名、ディレクトリの場合は[D] + ディレクトリ名を出力している。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/";
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
let name = entry.file_name();
if path.is_file() {
println!("[F]: {}", name.to_string_lossy());
}
else {
println!("[D]: {}", name.to_string_lossy());
}
}
Ok(())
}
エントリに関する詳細情報の取得
read_dir メソッドから得られる DirEntry オブジェクトには、metadata メソッドを使用してファイルやディレクトリに関する詳細情報を取得できる。
metadataメソッドは、ファイルのサイズ、タイムスタンプ、パーミッション等の情報を含む Metadata オブジェクトを返す。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/";
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let metadata = entry.metadata()?;
let name = entry.file_name();
if metadata.is_file() {
println!("[F]: {} (サイズ: {} bytes)", name.to_string_lossy(), metadata.len());
}
else {
println!("[D]: {}", name.to_string_lossy());
}
}
Ok(())
}
以下の例では、./testディレクトリに含まれるファイルとディレクトリの一覧を取得して、
ファイルの場合は[F] + ファイル名 + パス、ディレクトリの場合は[D] + ディレクトリ名 + パスを出力している。
use std::fs;
fn main() -> std::io::Result<()> {
let path = "./test/";
let entries = fs::read_dir(path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
let name = entry.file_name();
let metadata = entry.metadata()?;
if metadata.is_file() {
println!("[F]: {} {}", name.to_string_lossy(), path.display());
}
else {
println!("[D]: {} {}", name.to_string_lossy(), path.display());
}
}
Ok(())
}
再帰的なディレクトリの走査
ディレクトリを再帰的に走査する場合、walkdir クレートを使用すると便利である。
walkdirクレートは外部クレートなので、Cargo.tomlファイルに以下に示す設定を追加する必要がある。
[dependencies]
walkdir = "2"
WalkDirは、指定したディレクトリとそのサブディレクトリを再帰的に走査するイテレータを提供する。
use walkdir::WalkDir;
fn main() {
for entry in WalkDir::new("./test") {
match entry {
Ok(entry) => println!("{}", entry.path().display()),
Err(e) => eprintln!("エラー: {}", e),
}
}
}
// 出力例
./test
./test/address.txt
./test/doc
./test/doc/memo.txt
./test/img
./test/img/photo.jpg
ディレクトリのみをフィルタリングすることもできる。
use walkdir::WalkDir;
fn main() {
for entry in WalkDir::new("./test") {
match entry {
Ok(entry) => {
if entry.path().is_dir() {
println!("[D]: {}", entry.path().display());
}
}
Err(e) => eprintln!("エラー: {}", e),
}
}
}
※注意
エラーハンドリングは必須であり、Result型を適切に処理する必要がある。
パーミッション設定は、Unix系システムでは std::os::unix::fs::PermissionsExt トレイトを使用して行うことができる。