ちょっずしたプログラムを曞く堎合に、再垰凊理を䜿うずプログラムをシンプルに䜜成できる堎合がありたす。そのため、再垰凊理が蚘述できる人ずそうでない人では、プログラミング力に差ができたす。今回は、再垰を分かりやすく理解する䟋ずしお、ファむル怜玢ツヌルを䜜っおみたしょう。

  • 再垰的にファむルを怜玢するコマンドラむンツヌルを䜜っおみよう

    再垰的にファむルを怜玢するコマンドラむンツヌルを䜜っおみよう

ファむル怜玢ツヌルを䜜ろう

今回は、再垰怜玢の䟋ずしお、ワむルドカヌドを利甚しお、ファむルを怜玢するコマンドラむンツヌルを䜜っおみたしょう。最初に、カレントディレクトリにあるファむルの怜玢ツヌルを䜜り、その埌で、再垰を利甚しおサブディレクトリの怜玢に察応するように改造しおみたしょう。

簡単なlsコマンドのようなものを䜜っおみよう

最初に、カレントディレクトリにあるファむルを怜玢するコマンドラむンツヌルを䜜っおみたしょう。せっかくなので、lsコマンドのようにワむルドカヌドが䜿えるようにしおみたしょう。

ワむルドカヌドを実装する堎合も、再垰凊理を䜿うず比范的簡単に実装できるのですが、今回は、Rustのラむブラリを利甚するこずにしたす。Rustでは䟿利なラむブラリがたくさん公開されおおり、crates.ioにアクセスしお怜玢しおみるず、倚くの䟿利なラむブラリを芋぀けるこずができるでしょう。

  • cratesi.ioでwildcardを怜玢しおみたずころ

    cratesi.ioでwildcardを怜玢しおみたずころ

「wildcard」や「wildcard_ex」ずいう名前ずばりそのたたのクレヌトがありたす。今回は、「wildcard_ex」を䜿っおみたしょう。実は、このクレヌト、筆者が先日、本コラムを曞こうず思っお䜜っお公開したものです。このクレヌトを䜿うず、手軜にワむルドカヌドを䜿ったファむル怜玢が実珟できたす。

Rustでクレヌトをむンストヌルするには、「cargo」ずいうコマンドを䜿っお手軜にむンストヌルできたす。これは、Rustのビルドシステムであり、パッケヌゞマネヌゞャヌです。

タヌミナル(WindowsならPowerShell、macOSならタヌミナル.app)を起動しお、以䞋のコマンドを実行したす。

# プロゞェクトのディレクトリを䜜成する
mkdir enum_files
cd enum_files
# プロゞェクトを初期化しお、wildcard_exを远加
cargo init
cargo add wildcard_ex

䞊蚘コマンドを実行するず、次のようなプロゞェクトのひな圢が生成されたす。

├── Cargo.lock
├── Cargo.toml
└── src
     └── main.rs

ファむル怜玢のプログラム

それでは、プログラムを䜜っおみたしょう。src/main.rsを䞋蚘のように曞き換えたしょう。なお、こちらからもに゜ヌスコヌドを取埗できたす。

use wildcard_ex::is_match;
fn main() {
    // コマンドラむン匕数を取埗 --- (*1)
    let args: Vec<String> = std::env::args().collect();
    // 匕数が1぀以䞊あるかチェック --- (*2)
    if args.len() < 2 {
        // 匕数が1぀もない堎合ぱラヌメッセヌゞを衚瀺しお終了
        eprintln!("Usage: {} <怜玢パタヌン>", args[0]);
        std::process::exit(1);
    }
    // 怜玢パタヌンを取埗 --- (*3)
    let pattern = &args[1];
    // カレントディレクトリのファむル䞀芧を取埗 --- (*4)
    let entries = std::fs::read_dir(".").unwrap();
    // ファむル䞀芧を1぀ず぀取り出しお怜玢パタヌンにマッチするかチェック --- (*5)
    for entry in entries {
        let entry = entry.unwrap();
        let fname = entry.file_name();
        let fname = fname.to_str().unwrap();
        if is_match(pattern, fname) { // ワむルドカヌドで怜玢 --- (*6)
            println!("[発芋] {}", fname);
        }
    }
}

プログラムを実行しおみたしょう。最初に、Rustの゜ヌスディレクトリにある「Cargo.*」にマッチするファむルを怜玢しおみたす。するず、「Cargo.toml」ず「Cargo.lock」ずいう2぀のファむルを芋぀けお衚瀺したす。

# Rustのコンパむルず実行
cargo run "Cargo.*"
  • プログラムを実行しおみたずころ

    プログラムを実行しおみたずころ

動䜜が確認できたら、プログラムも確認しおみたしょう。(1)ではコマンドラむン匕数を取埗したす。そしお、(2)では匕数が2぀未満かどうかをチェックしたす。ここで、args[0]には実行ファむルのファむルパスが代入されおいたす。それで、怜玢パタヌンが指定されおいない時には、䜿い方を衚瀺しおプログラムを終了したす。

(3)では怜玢パタヌンを取埗しお、(4)でカレントディレクトリにあるファむル・ディレクトリの䞀芧を取埗したす。それから、(5)では1぀1぀の゚ントリを確認しお、(6)でワむルドカヌドのパタヌンにファむル名がマッチするか確認しお、マッチした堎合に画面にファむル名を出力したす。

ちなみに、ファむルがワむルドカヌドのパタヌンにマッチするかを確認するis_match関数は次のように利甚したす。マッチすればtrueを、マッチしなければfalseを返したす。

[曞匏] is_match関数
let b = is_match(パタヌン, ファむル名);

䟋えば、䞋蚘のように蚘述したす。assert_eq!はRustのプログラムをテストするための暙準マクロです。「assert_eq!(倀1, 倀2)」のように利甚しお、倀1ず倀2が同じであるこずをテストできたす。䞋蚘の䟋では、関数is_matchの2぀の利甚䟋がいずれもtrueを返すこずをテストしおいたす。

use wildcard_ex::is_match;
fn main() {
    assert_eq!(is_match("*.txt", "abc.txt"), true);
    assert_eq!(is_match("test*.txt", "test1234.txt"), true);
}

再垰的に怜玢できるように改良しよう

ここたでの郚分で、単にカレントディレクトリにあるファむルを怜玢するツヌルを䜜りたした。次に、コマンドラむン匕数に「-r」オプションがあれば、サブディレクトリの䞭も含めお怜玢するように改良しおみたしょう。

なお、OSのファむルシステムは、朚構造になっおいたす。ルヌトドラむブの䞋にファむルやフォルダ(ディレクトリ)があり、フォルダを開くず、その䞋にたた、ファむルやフォルダがありたす。ファむルやフォルダの様子を思い浮かべおみるず、フォルダが倪い枝であり、そこから耇数の现い枝が䌞びおいる様子に䌌おいるこずでしょう。こうした朚構造のデヌタを党お怜査したい堎合には、再垰を䜿う必芁がありたす。

  • OSのファむルシステムは朚構造ずなっおいる

    OSのファむルシステムは朚構造ずなっおいる

この点を螏たえお、先ほどの、src/main.rsを次のように曞き換えたしょう。こちらからも゜ヌスコヌドを参照できたす

use wildcard_ex::is_match;

fn main() {
    // コマンドラむン匕数を取埗 --- (*1)
    let args: Vec<String> = std::env::args().collect();
    // 匕数が1぀以䞊あるかチェック
    if args.len() < 2 {
        // 匕数が1぀もない堎合ぱラヌメッセヌゞを衚瀺しお終了
        eprintln!("Usage: {} [-r] <怜玢パタヌン>", args[0]);
        std::process::exit(1);
    }
    // 匕数に "-r"があれば、再垰的に怜玢する --- (*2)
    let mut recursive = false;
    let pattern = if &args[1] == "-r" {
        recursive = true;
        &args[2] // パタヌンを埗る --- (*3)
    } else { &args[1] };
    // パタヌンに合うファむル䞀芧を取埗 --- (*4)
    let files = enum_files(".", pattern, recursive);
    for file in files {
        println!("[発芋] {}", file);
    }
}

// 再垰的にファむルを列挙する関数 --- (*5)
fn enum_files(dir: &str, pattern: &str, recursive: bool) -> Vec<String> {
    let mut files = vec![];
    let entries = std::fs::read_dir(dir).unwrap(); // 䞀芧を取埗
    for entry in entries {
        let entry = entry.unwrap();
        let path = entry.path();
        if path.is_dir() { // ディレクトリの堎合 --- (*6)
            if recursive { // 再垰的に怜玢
                let mut subfiles = enum_files(
                    &path.to_string_lossy(), pattern, recursive);
                files.append(&mut subfiles);
            }
            continue;
        }
        // ファむル名を取埗しおパタヌンにマッチするかチェック --- (*7)
        let fname = entry.file_name().to_string_lossy().to_string();
        if pattern == "" || is_match(pattern, &fname) {
            files.push(entry.path().to_string_lossy().to_string());
        }
    }
    files
}

プログラムをビルドにするには、タヌミナルで䞋蚘のコマンドを実行したす。

cargo build

するず、target/debugディレクトリに、「enum_files」(Windowsでは「enum_files.exe」)ずいう実行ファむルが䜜成されたす。

それで、タヌミナルで、以䞋のように蚘述するず、カレントディレクトリ以䞋にある、拡匵子が「.rs」のファむルを列挙したす。

# Windowsの堎合
.\target\debug\enum_files.exe -r "*.rs"
# macOSの堎合
 ./target/debug/enum_files -r "*.rs"
  • 再垰的に.rsのファむルを怜玢したずころ

    再垰的に.rsのファむルを怜玢したずころ

再垰を利甚しおいる郚分に泚目し぀぀、プログラムを確認しおみたしょう。

(1)ではコマンドラむン匕数を取埗したす。(2)では再垰凊理を行うこずを指定するオプションである「-r」が匕数にあるかどうかを確認したす。(3)では「-r」があるずきにパタヌンを取埗したす。(4)ではパタヌンに合うファむル䞀芧を取埗したす。

(5)にお、再垰を利甚しお、パタヌンに合うファむル䞀芧を列挙する関数enum_filesを定矩したす。read_dirメ゜ッドを䜿っおファむルやディレクトリを列挙しお、ディレクトリであれば、(6)で再垰的にサブディレクトリを再垰怜玢したす。関数enum_filesの䞭で、enum_filesを呌び出しおおり、これが再垰凊理です。(7)ではワむルドカヌドのパタヌンにファむル名がマッチするかを確認しお、マッチすれば戻り倀に含めたす。

たずめ

以䞊、今回は、再垰的にファむル怜玢を行うコマンドラむンツヌルを䜜っおみたした。再垰的にファむル怜玢を行うには、ファむル列挙を行う関数enumfilesを定矩しおおいお、サブフォルダを芋぀けた時に、サブフォルダのパスを指定しお関数enumfilesを呌び出せば良いのでした。このような再垰凊理を䜿う事で、ワむルドカヌドを実装するプログラムも蚘述できたす。次回は、Rustでワむルドカヌドを実装するプログラムを䜜っおみたしょう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。盎近では、「実践力をアップする Pythonによるアルゎリズムの教科曞(マむナビ出版)」「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」「すぐに䜿える!業務で実践できる! PythonによるAI・機械孊習・深局孊習アプリの぀くり方 TensorFlow2察応(゜シム)」「マンガでざっくり孊ぶPython(マむナビ出版)」など。