Rustのコマンドライン引数
練習として作ってみる価値のある小さなプロジェクト(ToDoリストや単語カウンターなど)の多くは、値をソースコードに直接書き込むのではなく、コマンドラインから入力を受け取る必要があります。このページでは、その基本を学びます。
std::env::args で引数を読み取る
Section titled “std::env::args で引数を読み取る”use std::env;
fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args);}これを greet という名前のプログラムとしてコンパイルし、次のように実行したとします。
./greet Aliceすると、こう表示されます。
["greet", "Alice"]最初の要素は、いつもプログラム自身の名前です。あなたが実際に入力した引数は、インデックス1から始まります。
特定の引数を読み取る
Section titled “特定の引数を読み取る”use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("使い方: greet <名前>"); return; }
let name = &args[1]; println!("こんにちは、{name}さん!");}cargo run -- Alice で実行すると、次のようになります。
こんにちは、Aliceさん!引数なしで実行すると、プログラムが落ちる代わりに使い方のメッセージが表示されます。インデックスでアクセスする前に args.len() を確認しておけば、誰かが引数を渡し忘れてもパニックを防げます。
引数を数値に変換する
Section titled “引数を数値に変換する”引数はすべて String として渡されるので、数値として使いたいときは、型変換のページで学んだ .parse() がよく必要になります。
use std::env;
fn main() { let args: Vec<String> = env::args().collect();
if args.len() < 2 { println!("使い方: square <数値>"); return; }
match args[1].parse::<i32>() { Ok(n) => println!("{}", n * n), Err(_) => println!("正しい数値ではありません"), }}シンプルなフラグを自分で処理する
Section titled “シンプルなフラグを自分で処理する”フラグ(flag)とは、--shout のように何かをオンにする引数のことです。クレートに頼る前に、自分で処理するとどうなるかを見ておくと役に立ちます。試しやすくするコツは、処理を関数にまとめて、引数をスライス &[String] で受け取るようにすることです。そうすれば、main からは本物の引数を渡し、いろいろ試すときは手で作ったリストを渡せます。
fn run(args: &[String]) { let mut shout = false; let mut names = Vec::new();
// インデックス0(プログラム名)は飛ばす for arg in &args[1..] { if arg == "--shout" { shout = true; } else { names.push(arg); } }
if names.is_empty() { println!("使い方: greet [--shout] <名前>..."); return; }
for name in names { let line = format!("Hello, {name}!"); if shout { println!("{}", line.to_uppercase()); } else { println!("{line}"); } }}
fn main() { // 本物のプログラムならこう書く: // let args: Vec<String> = std::env::args().collect(); // ここではPlaygroundでも動くように、リストを手で作っている let args = vec![ String::from("greet"), String::from("--shout"), String::from("Ana"), String::from("Ben"), ]; run(&args);}実行結果:
HELLO, ANA!HELLO, BEN!あいさつの文字列を英語のままにしているのは、to_uppercase()(大文字にする)の効果が見えるようにするためです。日本語には大文字・小文字がないので、変換しても何も変わりません。
いくつか注目してほしい点があります。
&args[1..]は、プログラム名以外のすべての引数です。フラグはどこにあってもかまいません。greet Ana --shout Benでも動きます。- リストから
--shoutを消してもう一度実行すると、ふつうのあいさつになります。名前も消すと、使い方のメッセージが表示されます。 runはリストがどこから来たかを気にしないので、std::env::args().collect()に差し替えるのはmainの1行を変えるだけです。
フラグが1つか2つなら、これで十分です。でも、短い形(-s)、フラグのあとの値(--count 3)、--help メッセージまで欲しくなると、手書きのループはどんどん大きくなります。そうなったら clap に切り替えるタイミングです。
引数が2〜3個より多くなるなら:clap を使おう
Section titled “引数が2〜3個より多くなるなら:clap を使おう”フラグ(--verbose)や省略可能な値、わかりやすい --help の表示が必要になってくると、args を手作業でインデックス指定するやり方はすぐに手に負えなくなります。定番の解決策は clap(英語)クレートです。次のコマンドで追加します。
cargo add clap --features deriveuse clap::Parser;
#[derive(Parser)]struct Args { name: String,
#[arg(short, long, default_value_t = 1)] count: u32,}
fn main() { let args = Args::parse(); for _ in 0..args.count { println!("こんにちは、{}さん!", args.name); }}これだけで、引数の解析、入力チェック、そしてちゃんと動く --help メッセージが、構造体の定義から自動的に作られます。ちょっとしたスクリプトなら clap は不要ですが、何度も使うツールを作るなら、手作業の手間を大きく減らしてくれます。
Playgroundでは本物の引数を渡せないので、どちらの問題も、env::args().collect() が返すのとそっくりな Vec<String> を手で作るところから始めます。手元のマシンでは、それを本物に置き換えて cargo run -- ... を試してみてください。
1. 引数を一覧表示する
Section titled “1. 引数を一覧表示する”vecが与えられたとします。ユーザーが入力した引数の個数(プログラム名は数えない)を表示し、それから各引数を1から始まる番号付きで表示してください。
ヒント:個数は args.len() - 1 です。for i in 1..args.len() のループで、プログラム名のあとのインデックスを1つずつ取り出せます。
解答を見る
fn main() { // std::env::args().collect() から来たことにする let args = vec![ String::from("notes"), String::from("add"), String::from("牛乳を買う"), ];
println!("引数は{}個です", args.len() - 1); for i in 1..args.len() { println!("{i}: {}", args[i]); }}実行結果:
引数は2個です1: add2: 牛乳を買う本物のコマンドラインでは、スペースを含む値は引用符で囲んで入力します(cargo run -- add "buy milk")。シェルはそれを1つの文字列として渡すので、引数1つとして数えられます。インデックス0はやはりプログラム名なので、ループは1から始めています。
2. 数値を合計する
Section titled “2. 数値を合計する”vec!["sum", "4", "10", "x", "6"] が与えられたとします。正しい数値になっている引数をすべて足し合わせ、数値でないものには警告を表示してください。最後に合計を表示します。
ヒント:&args[1..] でループし、引数を数値に変換すると同じように match arg.parse::<i32>() を使います。ループの外に let mut total = 0; を用意しておきます。
解答を見る
fn main() { let args = vec![ String::from("sum"), String::from("4"), String::from("10"), String::from("x"), String::from("6"), ];
let mut total = 0; for arg in &args[1..] { match arg.parse::<i32>() { Ok(n) => total += n, Err(_) => println!("「{arg}」は数値ではないのでスキップします"), } } println!("合計: {total}");}実行結果:
「x」は数値ではないのでスキップします合計: 20引数はすべてテキストとして届きます。"4" も例外ではないので、1つずつパースする必要があります。おかしな入力をメッセージ付きで飛ばすほうが、クラッシュするよりずっと親切です。プログラムによっては、最初のおかしな入力で return して止めるほうが合っているかもしれません。
std::env::args().collect()で、コマンドライン引数をVec<String>として受け取れる。インデックス0は常にプログラム名。- インデックスでアクセスする前に
args.len()を確認すれば、引数がなくてもパニックしない。 Stringの引数を数値にするには、ほかの場面と同じく.parse()を使う。- 引数の処理は
&[String]を受け取る関数にまとめておくと、手で作ったリストで試せる。 - シンプルな引数が2〜3個より多くなるなら、定番で実績のある
clapクレートを選ぼう。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. env::args().collect() を呼んだとき、できあがる Vec の最初の要素は何ですか?
2. args にインデックスでアクセスする前に args.len() を確認するのはなぜですか?
3. 手作業で args をインデックス指定する方法にはなくて、clap クレートが提供してくれるものは何ですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- コマンドライン引数を受け付ける — The Rust Programming Language 日本語版:The Bookの
minigrepプロジェクトの始まりです。検索する単語とファイル名をコマンドラインから読み取ります。 std::env::args— 標準ライブラリのドキュメント(英語):args()が正確に何を返すのか、いつパニックするのか(正しいUnicodeでない引数のとき)がわかります。- 引数処理 — Rust By Example 日本語版:短い例に加えて、
matchで引数をパースする続きのページもあります。 - Command Line Applications in Rust(英語):引数の解析からテスト、配布まで、本格的なCLIツールの作り方を解説した無料のミニブックです。
- clap derive tutorial — docs.rs(英語):
clapでのフラグ、オプション、サブコマンド、入力チェックをステップごとに学べるガイドです。