コマンドライン引数を受け付ける
いつものように、cargo new
で新しいプロジェクトを作りましょう。プロジェクトをminigrep
と名付けて、
既に自分のシステムに存在するかもしれないgrep
ツールと区別しましょう。
最初の仕事は、minigrep
を二つの引数を受け付けるようにすることです: ファイル名と検索する文字列ですね。
つまり、cargo run
で検索文字列と検索を行うファイルへのパスと共にプログラムを実行できるようになりたいということです。
こんな感じにね:
$ cargo run searchstring example-filename.txt
今現在は、cargo new
で生成されたプログラムは、与えた引数を処理できません。
Crates.ioに存在する既存のライブラリには、
コマンドライン引数を受け付けるプログラムを書く手助けをしてくれるものもありますが、ちょうどこの概念を学んでいる最中なので、
この能力を自分で実装しましょう。
引数の値を読み取る
minigrep
が渡したコマンドライン引数の値を読み取れるようにするために、Rustの標準ライブラリで提供されている関数が必要になり、
それは、std::env::args
です。この関数は、minigrep
に与えられたコマンドライン引数のイテレータを返します。
イテレータについてはまだ議論していません(完全には第13章で講義します)が、とりあえずイテレータに関しては、
2つの詳細のみ知っていればいいです: イテレータは一連の値を生成することと、イテレータに対してcollect
関数を呼び出し、
イテレータが生成する要素全部を含むベクタなどのコレクションに変えられることです。
リスト12-1のコードを使用してminigrep
プログラムに渡されたあらゆるコマンドライン引数を読み取れるようにし、
それからその値をベクタとして集結させてください。
ファイル名: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{:?}", args); }
まず、std::env
モジュールをuse
文でスコープに導入したので、args
関数が使用できます。
std::env::args
関数は、2レベルモジュールがネストされていることに注目してください。
第7章で議論したように、希望の関数が2モジュール以上ネストされている場合、
関数ではなく親モジュールをスコープに導入するのが因習的です。そうすることで、
std::env
から別の関数も容易に使用することができます。また、
use std::env::args
を追記し、関数をargs
とするだけで呼び出すのに比べて曖昧でもありません。
というのも、args
は現在のモジュールに定義されている関数と容易に見間違えられるかもしれないからです。
args
関数と不正なユニコード引数のどれかが不正なユニコードを含んでいたら、
std::env::args
はパニックすることに注意してください。 プログラムが不正なユニコードを含む引数を受け付ける必要があるなら、代わりにstd::env::args_os
を使用してください。 この関数は、String
値ではなく、OsString
値を生成するイテレータを返します。ここでは、 簡潔性のためにstd::env::args
を使うことを選択しました。 なぜなら、OsString
値はプラットフォームごとに異なり、String
値に比べて取り扱いが煩雑だからです。
main
の最初の行でenv::args
を呼び出し、そして即座にcollect
を使用して、
イテレータをイテレータが生成する値全てを含むベクタに変換しています。
collect
関数を使用して多くの種類のコレクションを生成することができるので、
args
の型を明示的に注釈して文字列のベクタが欲しいのだと指定しています。Rustにおいて、
型を注釈しなければならない頻度は非常に少ないのですが、collect
はよく確かに注釈が必要になる一つの関数なのです。
コンパイラには、あなたが欲しているコレクションの種類が推論できないからです。
最後に、デバッグ整形機の:?
を使用してベクタを出力しています。引数なしでコードを走らせてみて、
それから引数二つで試してみましょう:
$ cargo run
--snip--
["target/debug/minigrep"]
$ cargo run needle haystack
--snip--
["target/debug/minigrep", "needle", "haystack"]
ベクタの最初の値は"target/debug/minigrep"
であることに注目してください。これはバイナリの名前です。
これはCの引数リストの振る舞いと合致し、実行時に呼び出された名前をプログラムに使わせてくれるわけです。
メッセージで出力したり、プログラムを起動するのに使用されたコマンドラインエイリアスによってプログラムの振る舞いを変えたい場合に、
プログラム名にアクセスするのにしばしば便利です。ですが、この章の目的には、これを無視し、必要な二つの引数のみを保存します。
引数の値を変数に保存する
引数のベクタの値を出力すると、プログラムはコマンドライン引数として指定された値にアクセスできることが説明されました。 さて、プログラムの残りを通して使用できるように、二つの引数の値を変数に保存する必要があります。 それをしているのがリスト12-2です。
ファイル名: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); let query = &args[1]; let filename = &args[2]; // {}を探しています println!("Searching for {}", query); // {}というファイルの中 println!("In file {}", filename); }
ベクタを出力した時に確認したように、プログラム名がベクタの最初の値、args[0]
を占めているので、
添え字1
から始めます。minigrep
が取る最初の引数は、検索する文字列なので、
最初の引数への参照を変数query
に置きました。2番目の引数はファイル名でしょうから、
2番目の引数への参照は変数filename
に置きました。
一時的にこれらの変数の値を出力して、コードが意図通りに動いていることを証明しています。
再度このプログラムをtest
とsample.txt
という引数で実行しましょう:
$ cargo run test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
素晴らしい、プログラムは動作しています!必要な引数の値が、正しい変数に保存されています。後ほど、 何らかのエラー処理を加えて、ユーザが引数を提供しなかった場合など、可能性のある特定のエラー状況に対処します; 今は、そのような状況はないものとし、代わりにファイル読み取り能力を追加することに取り組みます。