🐙
Rustでコマンドラインツール開発 オプションをいい感じに設定!
目標
お手軽にコマンドライン引数を取得しつつ、ヘルプ表示にも対応させる
例:異常終了(存在しないコマンドを指定)
terminal
$ command -l # 存在しないコマンドを指定
Unrecognized option: 'l'
書式: command [options]
Options:
-V, --value 1 or 2 or 3 etc..
入力した値を表示
-h, --help ヘルプを表示
-v, --version バージョンを表示
例:正常終了(コマンドライン引数を使用)
terminal
$ command -V 100
入力された値: 100
$ command -V "← 画面に表示されるよ"
入力された値: ← 画面に表示されるよ
$ command -hvV 複数オプション
書式: command [options]
Options:
-V, --value 1 or 2 or 3 etc..
入力した値を表示
-h, --help ヘルプを表示
-v, --version バージョンを表示
Version 0.1.0
入力された値: 複数オプション
プロジェクトの作成
以下のクレートを利用します。
- getopts
terminal
$ cargo new command # プロジェクトを作成
$ cd command # プロジェクトに移動
$ cargo add getopts # クレートをプロジェクトに追加
実装
import
getopts
からオプション設定に使用するOptions
ををimportしましょう。
他にも以下標準ライブラリも使用するため、一緒にimportします。
- env : コマンドライン引数を取得する
- exit : コマンドの異常終了、正常終了を可能にする
/src/main.rs
use getopts::Options;
use std::{env, process::exit};
コマンドライン引数の読み込み
fn main() {
// コマンドライン引数を取得
let args: Vec<String> = env::args().collect();
// プログラム名を取得("cat","ls"などのコマンド名)
let progname = args[0].clone();
}
オプションの作成
今回は下記のオプションに対応したいと思います。
- V: 入力した値を表示
- h: ヘルプを表示
- v: バージョンを表示
インスタンスを作成
fn main()
let mut opts = Options::new();
オプションを追加
オプションの種類も色々ありそうですが、本記事では2種類紹介します。
汎用的なオプション
fn main()
opts.optopt(
"V", // 短いオプション名を設定
"value", // 長いオプション名を設定
"入力した値を表示", // コマンドの説明を設定
"1 or 2 or 3 etc.." // 設定可能な値の例などのヒントを設定
);
フラグオプション
(コマンドライン引数にオプションが含まれるか、否かの情報だけを必要とするオプション)
fn main()
opts.optflag(
"h", // 短いオプション名を設定
"help", // 長いオプション名を設定
"ヘルプを表示" // コマンドの説明を設定
);
opts.optflag("v", "version", "バージョンを表示");
ヘルプの表示
usage関数を用意し、ヘルプ文言を出力する。
/src/main.rs
/**
* ヘルプの表示
*
* progname プログラム名
* opts オプション情報
*/
fn usage(progname: &str, opts: getopts::Options) {
// 書式の説明
let brief = format!("\n 書式: {progname} [options]");
// optopt, optflagなどで設定したオプションの情報を文字列に追加する
let usage: String = opts.usage(&brief);
eprint!("{usage}");
}
上記の関数を使用した出力例
表示例
書式: {progname} [options]
Options:
-V, --value 1 or 2 or 3 etc..
入力した値を表示
-h, --help ヘルプを表示
-v, --version バージョンを表示
コマンドラインの解析
fn main()
// コマンドライン引数から該当するオプションを取得
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
eprintln!("{f}");
usage(&progname, opts); //上のusage関数を使用
exit(1); // 異常終了
}
};
Err(f)
では、パースに失敗した原因を返してくれる
terminal
$ cargo build # ビルド
$ cd target/debug # 成果物の出力されたフォルダに移動
$ ./command -l # 存在しないコマンドを指定
Unrecognized option: 'l' # <= Err(f) から受け取った文言
書式: ./command [options]
Options:
-V, --value 1 or 2 or 3 etc..
入力した値を表示
-h, --help ヘルプを表示
-v, --version バージョンを表示
matches.free
でオプション以外のコマンドライン引数を取得できる
使用例
例
// 書式: command [options] ファイル名 AA BB
// ファイル名を取得する。
let file_name = match matches.free.get(0){
Some(s) => s,
None => exit(1) // 取得できない場合、異常終了
};
let AA = match matches.free.get(1){
Some(s) => s, None => exit(1)
};
let BB = match matches.free.get(1){
Some(s) => s, None => exit(1)
};
let file_name = matches.free[2]; // BB
コマンドライン引数による分岐
フラグオプションの参照方法
解析結果matches
のopt_present
を利用する
fn main()
// -h ヘルプの表示
if matches.opt_present("h") {
usage(&progname, opts);
}
// -v バージョン表示
if matches.opt_present("v") {
let version = option_env!("GIT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
eprintln!("\n Version {version} \n");
}
汎用オプションの参照方法
解析結果matches
のopt_str
を利用する
fn main()
// -V 入力値を利用
match matches.opt_str("V") {
Some(s) => println!("入力された値: {}", s), // 入力値を参照できる
None => {},
};
完成したソースコード
/src/main.rs
use std::{env, process::exit};
use getopts::Options;
/**
* ヘルプの表示
*
* progname プログラム名
* opts オプション情報
*/
fn usage(progname: &str, opts: getopts::Options) {
// 書式の説明
let brief = format!("\n 書式: {progname} [options]");
// optopt, optflagなどで設定したオプションの情報を文字列に追加する
let usage: String = opts.usage(&brief);
eprint!("{usage}");
}
fn main() {
// コマンドライン引数を取得
let args: Vec<String> = env::args().collect();
// プログラム名を取得("cat","ls"などのコマンド名)
let progname = args[0].clone();
// オプション設定
let mut opts = Options::new();
opts.optopt("V","value","入力した値を表示","1 or 2 or 3 etc..");
opts.optflag("h", "help", "ヘルプを表示");
opts.optflag("v", "version", "バージョンを表示");
// コマンドライン引数から該当するオプションを取得
let matches = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => {
eprintln!("{f}");
usage(&progname, opts);
exit(1);
}
};
// -h ヘルプの表示
if matches.opt_present("h") {
usage(&progname, opts);
}
// -v バージョン表示
if matches.opt_present("v") {
let version: &str = option_env!("GIT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION"));
eprintln!("\n Version {version} \n");
}
// -V 入力値を利用
match matches.opt_str("V") {
Some(s) => println!("入力された値: {}", s),
None => {},
};
}
課題
- ローカライズにも対応させると、近年の主要コマンドラインツールみたいに振る舞えそう
Discussion