コンテンツにスキップ

Rustのコマンドライン引数

練習として作ってみる価値のある小さなプロジェクト(ToDoリストや単語カウンターなど)の多くは、値をソースコードに直接書き込むのではなく、コマンドラインから入力を受け取る必要があります。このページでは、その基本を学びます。

use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}

これを greet という名前のプログラムとしてコンパイルし、次のように実行したとします。

Terminal window
./greet Alice

すると、こう表示されます。

["greet", "Alice"]

最初の要素は、いつもプログラム自身の名前です。あなたが実際に入力した引数は、インデックス1から始まります。

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() を確認しておけば、誰かが引数を渡し忘れてもパニックを防げます。

引数はすべて 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(英語)クレートです。次のコマンドで追加します。

Terminal window
cargo add clap --features derive
use 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 -- ... を試してみてください。

vec!["notes", "add", "牛乳を買う"](中身は String)が与えられたとします。ユーザーが入力した引数の個数(プログラム名は数えない)を表示し、それから各引数を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: add
2: 牛乳を買う
▶ 実際に試してみよう

本物のコマンドラインでは、スペースを含む値は引用符で囲んで入力します(cargo run -- add "buy milk")。シェルはそれを1つの文字列として渡すので、引数1つとして数えられます。インデックス0はやはりプログラム名なので、ループは1から始めています。

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 クレートを選ぼう。

理解度チェック

1. env::args().collect() を呼んだとき、できあがる Vec の最初の要素は何ですか?

2. args にインデックスでアクセスする前に args.len() を確認するのはなぜですか?

3. 手作業で args をインデックス指定する方法にはなくて、clap クレートが提供してくれるものは何ですか?

スコア: 0 / 3