[Rustのはじめ方] Part31: CLIツールの作成

Rust

Rustを使ってコマンドラインインターフェース(CLI)ツールを作成してみましょう!

これまでのステップで学んだ知識を活かして、実用的なアプリケーションを作成する最初のステップとして、CLIツールの開発に挑戦します。CLIツールは、開発者の日常的なタスクを自動化したり、特定の機能を提供したりするのに非常に便利です。Rustはそのパフォーマンスと信頼性から、CLIツールの開発にも適しています。

このセクションでは、簡単なCLIツールの作成を通じて、以下の内容を学びます。

  • Cargoを使った新しいプロジェクトの開始
  • コマンドライン引数の受け取りと解析
  • 基本的なファイル操作
  • 標準入出力の扱い
  • エラーハンドリング

1. プロジェクトのセットアップ ⚙️

まずは、Cargoを使って新しいバイナリプロジェクトを作成します。ターミナルで以下のコマンドを実行してください。

cargo new my_cli_tool
cd my_cli_tool

これにより、my_cli_tool という名前のディレクトリが作成され、基本的なプロジェクト構造がセットアップされます。src/main.rs がメインのソースファイルです。

2. コマンドライン引数の取得 📥

CLIツールは、ユーザーからの入力をコマンドライン引数として受け取ることがよくあります。Rustの標準ライブラリ std::env モジュールの args 関数を使って、引数を取得できます。

src/main.rs を編集して、引数を表示する簡単なプログラムを作成してみましょう。

use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("受け取った引数: {:?}", args);

    // 最初の引数 (args[0]) は通常、実行されたプログラムのパスです。
    if args.len() > 1 {
        println!("最初のユーザー指定引数: {}", args[1]);
    } else {
        println!("ユーザーからの引数はありませんでした。");
    }
}

プログラムを実行してみましょう。cargo run の後に引数を追加します。

cargo run -- arg1 arg2 "引数 3"

出力には、プログラムパスと指定した引数が表示されるはずです。

💡 env::args() はイテレータを返します。.collect() を使って Vec<String> に変換しています。

3. コマンドライン引数の解析 (clap) ✨

std::env::args はシンプルですが、複雑な引数(オプション、フラグ、サブコマンドなど)を扱うのは大変です。そこで、clap という非常に人気のあるクレートを使ってみましょう。clap は、コマンドライン引数の定義と解析を簡単に行うための強力な機能を提供します。

まず、Cargo.toml ファイルに clap を依存関係として追加します。features"derive" を含めることで、構造体から簡単にパーサーを生成できます。

[dependencies]
clap = { version = "4.5", features = ["derive"] } # 執筆時点の最新安定版を確認してください

次に、src/main.rs を修正して clap を使ってみます。

use clap::Parser;

/// ここにツールの説明を書きます。
/// シンプルなCLIツールの例です。
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)] // バージョン情報やヘルプメッセージを設定
struct Args {
    /// 表示する名前
    #[arg(short, long)] // -n または --name で指定可能にする
    name: String,

    /// 繰り返し回数
    #[arg(short, long, default_value_t = 1)] // -c または --count, デフォルト値1
    count: u64,

    /// 詳細表示フラグ
    #[arg(short, long, action = clap::ArgAction::SetTrue)] // -v または --verbose
    verbose: bool,
}

fn main() {
    let args = Args::parse(); // コマンドライン引数をパース

    if args.verbose {
        println!("受け取った引数: {:?}", args);
    }

    for _ in 0..args.count {
        println!("Hello, {}!", args.name);
    }
}

これで、より構造化された方法で引数を定義し、解析できるようになりました。clap は自動的にヘルプメッセージ (--help または -h) やバージョン情報 (--version または -V) も生成してくれます。

試してみましょう!

cargo run -- --name "Rustacean" -c 3
cargo run -- --help
cargo run -- -n World -v
⚠️ 以前は `structopt` というクレートも人気でしたが、`clap` v3以降にその機能が統合されました。これから始める場合は `clap` を使うのが一般的です。

4. ファイル操作 📄

CLIツールでは、ファイルの内容を読み込んだり、結果をファイルに書き出したりすることがよくあります。Rustの標準ライブラリ std::fs モジュールを使ってファイル操作を行いましょう。

例として、指定されたファイルの内容を読み込んで表示する機能を追加してみます。

use clap::Parser;
use std::fs;
use std::path::PathBuf;
use std::error::Error; // エラー処理用に Box を使う

/// ファイルの内容を表示するシンプルなCLIツール
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
    /// 読み込むファイルパス
    #[arg(value_name = "FILE")] // 引数の名前をヘルプに表示
    filepath: PathBuf, // PathBuf でパスを扱う
}

fn main() -> Result<(), Box<dyn Error>> { // main 関数から Result を返す
    let args = Args::parse();

    println!("読み込むファイル: {:?}", args.filepath);

    // ファイルを読み込む
    let content = fs::read_to_string(&args.filepath)?; // ?演算子でエラーを早期リターン

    println!("\nファイルの内容:");
    println!("{}", content);

    Ok(()) // 成功したら Ok(()) を返す
}

この例では、main 関数の戻り値を Result<(), Box<dyn Error>> に変更し、? 演算子を使ってファイル読み込み時のエラーを簡潔に処理しています。fs::read_to_string はファイルが存在しない場合や読み込み権限がない場合にエラーを返します。

適当なテキストファイル (例: test.txt) を作成し、実行してみてください。

echo "Hello from test file!" > test.txt
cargo run -- test.txt

ファイル書き込みには fs::write などが使えます。

5. 標準入出力 ↔️

他のコマンドと連携するために、標準入力からデータを受け取ったり、標準出力や標準エラー出力に結果を出力したりすることも重要です。

  • 標準入力: std::io::stdin() を使って取得します。
  • 標準出力: println! マクロは標準出力に書き込みます。
  • 標準エラー出力: eprintln! マクロは標準エラー出力に書き込みます。エラーメッセージやデバッグ情報はこちらに出力するのが一般的です。

例として、標準入力から受け取ったテキストを行番号付きで標準出力に書き出すツールを作ってみましょう。

use std::io::{self, BufRead}; // io と BufRead トレイトをインポート

fn main() -> io::Result<()> { // io関連のエラーを返す
    let stdin = io::stdin(); // 標準入力のハンドルを取得
    let mut line_number = 1;

    eprintln!("標準入力を待っています... Ctrl+D で終了"); // 標準エラー出力にメッセージ

    // stdin.lock() でロックを取得し、lines() で行ごとのイテレータを取得
    for line in stdin.lock().lines() {
        let line_content = line?; // 各行の読み込みエラーを処理
        println!("{:>4}: {}", line_number, line_content); // 右寄せ4桁で行番号を表示
        line_number += 1;
    }

    eprintln!("入力が終了しました。");

    Ok(())
}

実行して、ターミナルからテキストを入力してみてください。パイプ (|) を使って他のコマンドの出力を渡すこともできます。

cargo run
# (ここでテキストを入力し、Ctrl+Dで終了)

# 他のコマンドの出力をパイプで渡す例
ls -l | cargo run

まとめと次のステップ 🎉

このセクションでは、Rustで基本的なCLIツールを作成する方法を学びました。

  • cargo new でプロジェクトを作成しました。
  • std::env::argsclap クレートを使ってコマンドライン引数を扱いました。
  • std::fs モジュールで基本的なファイル操作を行いました。
  • 標準入出力 (stdin, stdout, stderr) を扱いました。
  • Result? 演算子を使ったエラーハンドリングを行いました。

これで、自分だけの便利なツールを作るための基礎が身につきました!さらに高度なCLIツールを作成するには、以下のようなトピックを探求してみると良いでしょう。

  • サブコマンドの実装 (clap で可能)
  • 設定ファイル (TOML, YAML, JSONなど) の読み込み (serde, toml, serde_yaml, serde_json クレート)
  • より高度なエラーハンドリング (anyhow, thiserror クレート)
  • 外部APIとの連携 (reqwest クレートなど)
  • 非同期処理 (tokio)

次のステップでは、Webサーバーの構築に挑戦します。ここでの学びを活かして、さらに複雑なアプリケーション開発に進みましょう!🚀

コメント

タイトルとURLをコピーしました