Rustの基礎 - ファイル

提供:MochiuWiki - SUSE, Electronic Circuit, PCB
ナビゲーションに移動 検索に移動

概要

ローカルに保存されたファイルからデータを読み込んだり、ファイルへデータを書き込むことができる。
標準ライブラリの std::fs モジュールを使用して、ファイルの作成、読み込み、書き込み、削除などの操作を行うことができる。


ファイルのオープン / クローズ

ファイルを開く

std::fs::File 構造体の open 関数を使用してファイルを開くことができる。

 use std::fs::File;
 
 let file = File::open("myfile.txt");


open メソッドは、Result<File, std::io::Error> 型を返す。
正常に開くことができる場合、Ok(File) を返す。
正常に開くことができない場合、Err(std::io::Error) を返す。

ファイルを開く際のモードを指定する場合は、OpenOptions 構造体を使用する。

 use std::fs::OpenOptions;
 
 let file = OpenOptions::new()
    .read(true)
    .write(true)
    .open("myfile.txt");


OpenOptions 構造体では以下に示すオプションを指定できる。

  • read(true)
    読み込み用
  • write(true)
    書き込み用
  • append(true)
    追記用 (ファイルの末尾に追加)
  • truncate(true)
    ファイルの中身をクリア
  • create(true)
    ファイルが存在しない場合は作成
  • create_new(true)
    ファイルが存在しない場合のみ作成 (存在する場合はエラー)


以下の例では、様々なモードでファイルを開いている。

 use std::fs::File;
 use std::fs::OpenOptions;
 
 // 読み込み専用
 let f1 = File::open("myfile.txt");
 
 // 読み書き可能
 let f2 = OpenOptions::new()
    .read(true)
    .write(true)
    .open("myfile.txt");
 
 // 書き込み専用(ファイルの中身をクリア)
 let f3 = OpenOptions::new()
    .write(true)
    .truncate(true)
    .create(true)
    .open("myfile.txt");
 
 // 追記モード
 let f4 = OpenOptions::new()
    .append(true)
    .create(true)
    .open("myfile.txt");


エラー処理を行う場合は、match 式 や unwrapexpect メソッドを使用する。

 use std::fs::File;
 
 // matchを使用する場合
 let file = match File::open("myfile.txt") {
    Ok(file) => file,
    Err(error) => panic!("ファイルを開けません: {:?}", error),
 };
 
 // unwrapを使用する場合
 let file = File::open("myfile.txt").unwrap();
 
 // expectを使用する場合
 let file = File::open("myfile.txt").expect("ファイルを開けません");


ファイルを閉じる

Rustでは、変数がスコープを抜けると自動的にファイルが閉じられる。
これは、File 構造体が Drop トレイトを実装しているためである。

 use std::fs::File;
 
 {
    let file = File::open("myfile.txt").unwrap();
 
    // ファイルを使用する処理
    // ...略
 }  // ここでfileがスコープを抜け、自動的にクローズされる


明示的にファイルを閉じる必要がある場合は、drop メソッドを使用する。

 use std::fs::File;
 
 let file = File::open("myfile.txt").unwrap();
 // ファイルを使用する処理
 drop(file);  // 明示的にクローズ



テキストファイルの読み込み

全体を読み込む (read_to_stringメソッド)

ファイルに含まれるテキストを全て読み込む場合、std::fs::read_to_string メソッドを使用する。

 use std::fs;
 
 let contents = fs::read_to_string("myfile.txt")
    .expect("ファイルの読み込みに失敗しました");
 
 println!("{}", contents);


または、FileRead トレイトを使用する方法もある。

 use std::fs::File;
 use std::io::Read;
 
 let mut file = File::open("myfile.txt").unwrap();
 let mut contents = String::new();
 
 file.read_to_string(&mut contents).unwrap();
 println!("{}", contents);


行単位で読み込む (BufReaderとlines)

ファイルから1行ずつファイルの内容を読み込む場合、BufReaderlinesメソッドを使用する。

 use std::fs::File;
 use std::io::{BufRead, BufReader};
 
 let file = File::open("myfile.txt").unwrap();
 let reader = BufReader::new(file);
 
 for line in reader.lines() {
    let line = line.unwrap();
    println!("{}", line);
 }


lines メソッドは、各行の末尾の改行文字を自動的に取り除く。
エラー処理を含めた実装例は以下の通りである。

 use std::fs::File;
 use std::io::{BufRead, BufReader};
 
 let file = File::open("myfile.txt").expect("ファイルを開けません");
 let reader = BufReader::new(file);
 
 for line in reader.lines() {
    match line {
       Ok(content) => println!("{}", content),
       Err(error) => eprintln!("行の読み込みエラー: {}", error),
    }
 }


改行文字を含めて読み込みたい場合は、read_line メソッドを使用する。

 use std::fs::File;
 use std::io::{BufRead, BufReader};
 
 let file = File::open("myfile.txt").unwrap();
 let mut reader = BufReader::new(file);
 let mut line = String::new();
 
 while reader.read_line(&mut line).unwrap() > 0 {
    print!("{}", line);  // 改行が含まれる
    line.clear();
 }


行単位で分割してベクターとして取得する

ファイル全体の読み込み後、読み込みしたデータを行単位で分割して、Vec の要素として追加することができる。

 use std::fs::File;
 use std::io::{BufRead, BufReader};
 
 let file = File::open("myfile.txt").unwrap();
 let reader = BufReader::new(file);
 
 let lines: Vec<String> = reader
    .lines()
    .map(|line| line.unwrap())
    .collect();
 
 for line in &lines {
    println!("{}", line);
 }


エラー処理を適切に行う場合は、以下に示すように実装する。

 use std::fs::File;
 use std::io::{BufRead, BufReader};
 
 let file = File::open("myfile.txt").expect("ファイルを開けません");
 let reader = BufReader::new(file);
 
 let lines: Result<Vec<String>, _> = reader.lines().collect();
 
 match lines {
    Ok(lines) => {
       for line in lines {
          println!("{}", line);
       }
    }
    Err(error) => eprintln!("読み込みエラー: {}", error),
 }



テキストファイルの書き込み

テキストファイルへ書き込むためにファイルを開く場合、OpenOptions を使用して適切なモードを指定する。

 use std::fs::OpenOptions;
 
 // 書き込み専用 (上書き)
 let f1 = OpenOptions::new()
    .write(true)
    .truncate(true)
    .create(true)
    .open("myfile.txt");
 
 // 追記モード
 let f2 = OpenOptions::new()
    .append(true)
    .create(true)
    .open("myfile.txt");
 
 // 新規作成のみ
 let f3 = OpenOptions::new()
    .write(true)
    .create_new(true)
    .open("myfile.txt");


書き込み / 上書き

ファイルに書き込む場合、ファイルが存在しない時はファイルを新規作成、ファイルが存在する時は上書きする。

テキストファイルに書き込む場合は、write メソッド または write_allメソッドを使用する。

 use std::fs::OpenOptions;
 use std::io::Write;
 
 let mut file = OpenOptions::new()
    .write(true)
    .truncate(true)
    .create(true)
    .open("myfile.txt")
    .unwrap();
 
 file.write_all(b"Hello, World!\n").unwrap();


文字列を書き込む場合は、as_bytes() メソッドでバイト列に変換するか、write! マクロを使用する。

 use std::fs::OpenOptions;
 use std::io::Write;
 
 let mut file = OpenOptions::new()
    .write(true)
    .truncate(true)
    .create(true)
    .open("myfile.txt")
    .unwrap();
 
 // as_bytesメソッドを使用
 file.write_all("こんにちは\n".as_bytes()).unwrap();
 
 // write!マクロを使用
 write!(file, "お元気ですか?\n").unwrap();


簡潔に書き込む場合は、std::fs::write メソッドを使用する。

 use std::fs;
 
 fs::write("myfile.txt", "こんにちは\n").expect("書き込みに失敗しました");


複数の文字列をまとめてファイルに書き込む場合は、ベクターをループで処理する。

 use std::fs::OpenOptions;
 use std::io::Write;
 
 let mut file = OpenOptions::new()
    .write(true)
    .truncate(true)
    .create(true)
    .open("myfile.txt")
    .unwrap();
 
 let datalist = vec!["こんにちは\n", "お元気ですか?\n", "それではまた\n"];
 for data in datalist {
    file.write_all(data.as_bytes()).unwrap();
 }


追記

ファイルに追記する場合、ファイルが存在しない時はファイルを新規作成、ファイルが存在する時はファイルの最後に追記する。

 use std::fs::OpenOptions;
 use std::io::Write;
 
 let mut file = OpenOptions::new()
    .append(true)
    .create(true)
    .open("myfile.txt")
    .unwrap();
 
 file.write_all("こんにちは\n".as_bytes()).unwrap();
 
 let datalist = vec!["お元気ですか?\n", "それではまた\n"];
 for data in datalist {
    file.write_all(data.as_bytes()).unwrap();
 }


ファイルを新規作成して書き込む

ファイルが存在しない場合のみファイルを新規作成して書き込む場合、create_new(true) メソッドを使用する。
ファイルが存在する場合は、エラーが発生する。

 use std::fs::OpenOptions;
 use std::io::Write;
 
 let mut file = OpenOptions::new()
    .write(true)
    .create_new(true)
    .open("myfile.txt")
    .expect("ファイルが既に存在します");
 
 file.write_all("こんにちは\n".as_bytes()).unwrap();
 
 let datalist = vec!["お元気ですか?\n", "それではまた\n"];
 for data in datalist {
    file.write_all(data.as_bytes()).unwrap();
 }



バイナリファイル

バイナリファイルを読み込む場合、File::open メソッドを使用してファイルを開く。
Rustでは、テキストモードとバイナリモードの区別はなく、全てのファイルはバイナリとして扱われる。

 use std::fs::File;
 
 let file = File::open("myfile.dat").unwrap();


バイナリファイルのデータを読み込む場合、read メソッドを使用する。

 use std::fs::File;
 use std::io::Read;
 
 let mut file = File::open("myfile.dat").unwrap();
 let mut buffer = Vec::new();
 
 file.read_to_end(&mut buffer).unwrap();
 println!("{:?}", buffer);


指定したバイト数だけ読み込む場合は、固定サイズのバッファを使用する。

 use std::fs::File;
 use std::io::Read;
 
 let mut file = File::open("myfile.dat").unwrap();
 let mut buffer = [0u8; 1024];  // 1024バイトのバッファ
 
 let bytes_read = file.read(&mut buffer).unwrap();
 println!("読み込んだバイト数: {}", bytes_read);


簡潔にファイル全体を読み込む場合は、std::fs::read メソッドを使用する。

 use std::fs;
 
 let data = fs::read("myfile.dat").expect("ファイルの読み込みに失敗しました");
 println!("{:?}", data);


バイナリファイルにデータを書き込む場合、write メソッド または write_all メソッドを使用する。

 use std::fs::File;
 use std::io::Write;
 
 let mut file = File::create("myfile.dat").unwrap();
 file.write_all(b"ABCDEFG").unwrap();


簡潔に書き込む場合は、std::fs::write メソッドを使用する。

 use std::fs;
 
 fs::write("myfile.dat", b"ABCDEFG").expect("書き込みに失敗しました");
 
 let data = fs::read("myfile.dat").unwrap();
 println!("{:?}", data);
 
 // 出力
 [65, 66, 67, 68, 69, 70, 71]


以下の例では、画像ファイルを読み込み、読み込んだデータを新規作成したバイナリファイルに書き込んでいる。

 use std::fs;
 use std::io::{Read, Write};
 
 // 方法1 : 1度に全て読み書き
 let data = fs::read("Sample1.png").expect("読み込みエラー");
 fs::write("Sample2.png", data).expect("書き込みエラー");
 
 // 方法2 : バッファを使用して1バイトずつ処理
 use std::fs::File;
 
 let mut fr = File::open("Sample1.png").unwrap();
 let mut fw = File::create("Sample2.png").unwrap();
 let mut buffer = [0u8; 1];
 
 loop {
    let bytes_read = fr.read(&mut buffer).unwrap();
    if bytes_read == 0 {
       break;
    }
    fw.write_all(&buffer[..bytes_read]).unwrap();
 }



ファイルの作成 / 削除

ファイルの作成

空のファイルを新規作成する場合は、File::create メソッドを使用する。

 use std::fs::File;
 
 let file = File::create("user.txt").expect("ファイルの作成に失敗しました");


File::create メソッドは、ファイルが存在しない場合は新規作成し、存在する場合は内容を空にする。

ファイルが存在しない場合のみ作成したい場合は、OpenOptions を使用する。

 use std::fs::OpenOptions;
 
 let file = OpenOptions::new()
    .write(true)
    .create_new(true)
    .open("user.txt")
    .expect("ファイルが既に存在するか、作成に失敗しました");


ファイルの削除

ファイルを削除する場合は、std::fs::remove_file メソッドを使用する。

 use std::fs;
 
 fs::remove_file("name.txt").expect("ファイルの削除に失敗しました");


存在しないファイルを削除しようとすると、エラーが発生する。
エラーを無視する場合は、以下のように実装する。

 use std::fs;
 
 match fs::remove_file("name.txt") {
    Ok(_) => println!("ファイルを削除しました"),
    Err(e) => eprintln!("削除エラー: {}", e),
 }
 
 // または、Result型を無視
 let _ = fs::remove_file("name.txt");



ファイルの存在確認

指定したパスが示すファイルが存在するかどうかを確認する場合、std::path::Pathexists メソッドを使用する。

 use std::path::Path;
 
 let path = Path::new("myfile.txt");
 if path.exists() {
    println!("ファイルは存在します");
 }
 else {
    println!("ファイルは存在しません");
 }


exists メソッドは、ファイルまたはディレクトリが存在する場合に true を返すことに注意する。

ファイルとディレクトリを判別する場合は、is_file メソッド と is_dir メソッドを使用する。

 use std::path::Path;
 
 let path = Path::new("myfile.txt");
 
 if path.is_file() {
    println!("これはファイルです");
 }
 else if path.is_dir() {
    println!("これはディレクトリです");
 }
 else {
    println!("存在しません");
 }


ファイルのメタデータを取得して詳細な情報を確認することもできる。

 use std::fs;
 
 match fs::metadata("myfile.txt") {
    Ok(metadata) => {
       if metadata.is_file() {
          println!("ファイルです。サイズ: {} バイト", metadata.len());
       }
       else if metadata.is_dir() {
          println!("ディレクトリです");
       }
    }
    Err(_) => println!("ファイルは存在しません"),
 }



ファイル名の変更

ファイル名を変更する場合は、std::fs::rename メソッドを使用する。

 use std::fs;
 
 fs::rename("book.txt", "memo.txt").expect("リネームに失敗しました");


ファイルを別のディレクトリに移動することもできる。

 use std::fs;
 
 fs::rename("book.txt", "back/memo.txt").expect("移動に失敗しました");


移動先のディレクトリが存在しない場合、エラーが発生する。

エラー処理を含めた実装例は以下の通りである。

 use std::fs;
 
 match fs::rename("book.txt", "memo.txt") {
    Ok(_) => println!("リネームに成功しました"),
    Err(error) => eprintln!("リネームエラー: {}", error),
 }


Windows環境では、移動先のファイルが既に存在する場合、エラーが発生する。
Unix系では、既存のファイルを上書きする。

 use std::fs;
 
 // 既にmemo.txtが存在する場合の動作はOSに依存
 fs::rename("book.txt", "memo.txt").expect("リネームに失敗しました");



条件に一致するファイルの一覧の取得

同階層のファイルの一覧の取得

指定した条件に一致するファイルの一覧を取得する場合は、glob クレートを使用する。

まず、Cargo.tomlファイルに依存関係を追加する。

 [dependencies]
 glob = "0.3"


glob メソッドを使用してパターンマッチングを行う。

 use glob::glob;
 
 for entry in glob("*.txt").expect("パターンの読み込みに失敗しました") {
    match entry {
       Ok(path) => println!("{:?}", path),
       Err(e) => eprintln!("エラー: {}", e),
    }
 }


パターンには以下の特殊文字を指定できる。

  • *
    0文字以上の任意の文字
  • ?
    1文字の任意の文字
  • [abc]
    括弧の中のいずれかの文字


以下の例では、末尾が.jpgのファイルを取得している。

 use glob::glob;
 
 for entry in glob("test/*.jpg").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }


以下の例では、bから始まるファイルを取得している。

 use glob::glob;
 
 for entry in glob("test/b*").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }


以下の例では、3文字の任意の文字で始まり、末尾が.txtのファイルを取得している。

 use glob::glob;
 
 for entry in glob("test/???.txt").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }


以下の例では、文字の範囲を指定している。

 use glob::glob;
 
 // aからeまでの文字で始まり、末尾が.txtのファイル
 for entry in glob("test/[a-e]*.txt").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }
 
 // fからzまでの文字で始まり、末尾が.txtのファイル
 for entry in glob("test/[f-z]*.txt").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }


再帰的にファイルの一覧の取得

サブディレクトリを含めて再帰的にファイルを検索する場合、パターンに ** を使用する。

 use glob::glob;
 
 for entry in glob("test/**/*.txt").unwrap() {
    if let Ok(path) = entry {
       println!("{}", path.display());
    }
 }
 
 // 出力例
 // test/book.txt
 // test/cup.txt
 // test/pen.txt
 // test/doc/sample.txt
 // test/back/2020.txt
 // test/back/old/2017.txt


標準ライブラリのみを使用する場合は、std::fs::read_dir メソッドを再帰的に呼び出す。

 use std::fs;
 use std::path::Path;
 
 fn visit_dirs(dir: &Path, ext: &str) -> std::io::Result<()> {
    if dir.is_dir() {
       for entry in fs::read_dir(dir)? {
          let entry = entry?;
          let path = entry.path();
          if path.is_dir() {
             visit_dirs(&path, ext)?;
          }
          else if let Some(extension) = path.extension() {
             if extension == ext {
                println!("{}", path.display());
             }
          }
       }
    }
    Ok(())
 }
 
 fn main() {
    visit_dirs(Path::new("test"), "txt").unwrap();
 }