Mono.Optionsを使ったコマンドラインオプションのパース

コマンドラインオプション

コマンドラインオプションとはコマンドの後に付ける文字列のことで、コマンドのオプション動作を指定する為にコマンドライン上でコマンドの後に指定されることから、コマンドラインオプションと呼ばれます。起動スイッチと呼ばれたり、単純にオプションやスイッチと呼ばれる場合もあります。

たとえば

$ ls -al

とコマンドを入力した場合、-alの部分がコマンドラインオプションになります。

コマンドラインオプションのスタイル

コマンドラインオプションのスタイルには大きく分けると2種類のスタイルが有り、WindowsはDOSの頃からVMSスタイルの/(スラッシュ)からオプションが始まるスタイルで、Unix,Linuxは-(ハイフン)から始まり一重のハイフンでは一文字のオプション(Exp. -h)で、二重のハイフンでは冗長的に表現したオプション(Exp. –help)を指定する事が慣習になっていると思います。

.NETアプリケーションでのコマンドラインオプション

.NETのアプリケーションではコマンドラインオプションはMainメソッド(エントリーポイントメソッド)の引数String[] argsに格納されてプログラムに渡されます。

> hoge -n Tadahiro --telephone=090xxxxxxxx -a 41

たとえば上のようにコマンドラインでコマンドとコマンドラインオプションが入力された場合、argsの内容は以下のようになります。

{"-n", "Tadahiro", "--telephone=090xxxxxxxx", "-a", "41"}

あとはargsの内容をパースして、パース結果を基に適切な処理を行うようにするわけですが、あまり考えずに書くとメンテを行いづらいコードになってしまいますし、毎回毎回で飽きてしまいます。

結構くだらないことなので、こういうことはライブラリの力を借りて簡単に済ませたいところです。

.NET Framework 4のプレビューで一瞬標準ライブラリにコマンドラインオプションのパーサーライブラリが含まれそうになったのですが、結局含まれることはありませんでした。

とはいってもなにも無いわけでは無く、オープンソースのプロジェクトがいくつかあり、その一つが今回紹介するMono.Optionsになります。

Mono.Options

Mono.OptionsはもともとNDesk.Optionsと言うライブラリで、Monoに組み込まれ、Mono.Optionsに名前を変えました。Mono.OptionsはUnixスタイルなコマンドラインオプションをプログラムに加えるためのライブラリで、属性を使う他のライブラリに比べて綺麗に書けると思います。

導入

Mono.Optionsを自分の開発プロジェクトに加えるにはNuGetを使うのが簡単です。

Mono.Optionsを検索してインストールします。

fizzbuzz001

インストールすると以下のようにMono.Optionsのソースがプロジェクトに追加されます。

fizzbuzz002

追加されるのはあくまでもソースファイルなので注意して下さい。

使用方法

コーディングスタイルとしては、オプションの設定、パース、エラー処理が基本的な処理の流れです。

オプションの設定

オプションの設定は、OptionSetクラスOptionクラスを追加することで行います。

var p = new OptionSet() {
    //オプションとオプションの説明、そのオプションの引数に対するアクションを定義する
    {"f|fizz=", "fizz text.", v => fizz = v},
    {"b|buzz=", "buzz text.", v => buzz = v},
    {"z|fizzbuzz=", "FizzBuzz text.", v => fizzBuzz = v},
    //string型以外の値をとりたい場合は以下のようにする
    {"m|max=", "Max count.", (int v) => max = v},
    //VALUEをとらないオプションは以下のようにnullとの比較をしてTrue/Falseの値をとるようにする
    {"h|help", "show help.", v => showHelp = v != null}

};

また、上の記述は以下のようにAddメソッドを使って書き直すことが出来ます。

var p = new OptionSet()
    .Add("f|fizz=", "fizz text.", v => fizz = v)
    .Add("b|buzz=", "buzz text.", v => buzz = v)
    .Add("z|fizzbuzz=", "FizzBuzz text.", v => fizzBuzz = v)
    .Add("m|max=", "Max count.", (int v) => max = v)
    .Add("h|help", "show help.", v => showHelp = v != null)

Optionオブジェクトの設定は基本的に{[オプションの文字列],[オプションの説明],[オプションのアクション]}となります。

オプションの文字列の指定方法をBN記法で表すと以下のようになります。(以下はOptions.csのコメントから抜粋。)

//
// Option format strings:
//  Regex-like BNF Grammar: 
//    name: .+
//    type: [=:]
//    sep: ( [^{}]+ | '{' .+ '}' )?
//    aliases: ( name type sep ) ( '|' name type sep )*

オプションが複数の名前を持つ場合|で区切ります。次にオプション名に=がつくとそのオプションにさらに何らかの値を与えることが必要であること示しまします。:がオプション名についた場合、そのオプション対して何らかの値を与えるか、与えないかの選択になります。

以下にオプション名の例を示します。

"f|fizz="

この場合”-f”オプションと”-fizz”オプションは同じ意味のオプションで、このオプションにはさらに何らかの値を与えることが必要出ると示していることになります。

また、ここで設定したオプションはコマンドライン上では先頭に-, –, /のいずれかをつけてオプションのキーとして指定します。OptionsのParseメソッドは引数で与えられたstring型の配列中の文字列にこのいずれかが先頭に着いていればそれに続く文字列がオプションと判断して処理します。したがって、Optionsを使う場合コマンドライン上でした”-f”, “–f”, “/f”は等価になります。

また、オプションのキーに対して値を与える場合はオプションのキーの後に=, :, [半角空白]のいずれかを付けて値を設定します。従って、”-f=value”, “-f:value”, “-f value”は同じようにParseメソッドでは判断されます。

オプションの値を文字列以外で取りたい場合にはアクションのラムダ式で以下のように型を指定します。

{"m=|max=", "Max count.", (int v) => max = v}

またオプションはキーの有無だけが必要で値が必要ない場合もあると思います。その場合には以下のようにオプションの有無でbool値を取得することが出来ます。

//オプションが指定された場合にTrueの値を取りたい
{"h|help", "show help.", v => showHelp = v != null}

パース

OptionSetクラスのParseメソッドを使います。Parseメソッドはstring型の配列を取るのでMainメソッドのargsをそのまま渡せば良いです。また、Parseメソッドは、パースし切れな買ったものをstring型のリストで返すので、必要に応じて受けるようにします。

また、Parseメソッドはオプションのパースに失敗した場合にはOptionExceptionをスローするので、以下のようにtry catchで処理をするようにします。

List extra;
//OptionSetクラスのParseメソッドでOptionSetの内容に合わせて引数をパースする。
//Paseメソッドはパース仕切れなかったコマンドラインのオプションの内容をstring型のリストで返す。
try {
    extra = p.Parse(args);
    extra.ForEach(t => Debug.WriteLine(t));
}
//パースに失敗した場合OptionExceptionを発生させる
catch (OptionException e) {
    Console.WriteLine("FizzBuzz:");
    Console.WriteLine(e.Message);
    Console.WriteLine("Try `CommandLineOption --help' for more information.");
    return;
}

以上で、主なところの説明は終わりです。これだけ押さえておけば大抵の用途では大丈夫なのでは無いかと。後はOptions.csのコメント欄に説明があるのと、オンラインのドキュメントとソース自体を参考にすれば良いと思います。

まとめ

  • Mono.Optionsを使うことでコマンドラインオプションのパースが容易になります。
  • OptionSetにOptionオブジェクトを追加し、OptionSetのParseコマンドでコマンドラインオプションをパースします。Parseメソッドはパースしきれなかったものをstringのリストで返します。
  • Parseメソッドはパースに失敗したらOptionExceptionがスローするので、try~catchで処理します。

全体コード

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using Mono.Options; //追加


namespace FizzBuzz
{
    /// 
    /// Mono.Optionの使用サンプル
    /// 
    class Program
    {
        /// 
        /// コマンドラインオプションでとった文字列と最大数でFizzBuzzを表示させる
        /// 
        /// 
        private static void Main(string[] args) {

            args.ToList().ForEach(t => Console.Write(t + ", "));
            Console.WriteLine();

            string fizzBuzz=null;
            string fizz=null;
            string buzz=null;
            var max = 100;
            bool showHelp = false;

            //コマンドラインオプションを設定する

            var p = new OptionSet() {
                //オプションとオプションの説明、そのオプションの引数に対するアクションを定義する
                {"f=|fizz=", "fizz text.", v => fizz = v},
                {"b=|buzz=", "buzz text.", v => buzz = v},
                {"z=|fizzbuzz=", "FizzBuzz text.", v => fizzBuzz = v},
                //string型以外の値をとりたい場合は以下のようにする
                {"m=|max=", "Max count.", (int v) => max = v},
                //VALUEをとらないオプションは以下のようにnullとの比較をしてTrue/Falseの値をとるようにする
                {"h|help", "show help.", v => showHelp = v != null}

            };
            List extra;
            //OptionSetクラスのParseメソッドでOptionSetの内容に合わせて引数をパースする。
            //Paseメソッドはパース仕切れなかったコマンドラインのオプションの内容をstring型のリストで返す。
            try {
                extra = p.Parse(args);
                extra.ForEach(t => Debug.WriteLine(t));
            }
            //パースに失敗した場合OptionExceptionを発生させる
            catch (OptionException e) {
                Console.WriteLine("FizzBuzz:");
                Console.WriteLine(e.Message);
                Console.WriteLine("Try `CommandLineOption --help' for more information.");
                return;
            }
            //-h|--helpオプションの時にはUasgeを表示する。
            if (showHelp) {
                ShowUsage(p);
                return;
            }
            //-f|-fizzオプションに値が無い場合にはメッセージ表示
            if (fizz == null) {
                ShowUsage(p);
                return;
            }
            if (buzz == null) {
                ShowUsage(p);
                return;
            }
            if (fizzBuzz == null) {
                ShowUsage(p);
                return;
            }

            //FizzBuzzの表示
            Enumerable.Range(1, max).ToList().ForEach(
                i =>; Console.WriteLine((i%3 == 0 && i%5 == 0)
                    ? fizzBuzz
                    : (i%5 == 0) ? buzz : (i%3 == 0) ? fizz : i.ToString()));


        }

        // Uasgeを表示する
        private static void ShowUsage(OptionSet p) {
            Console.Error.WriteLine("Usage:FizzBuzz [OPTIONS]");
            Console.Error.WriteLine();
            p.WriteOptionDescriptions(Console.Error);
        }


    }
}

3 thoughts on “Mono.Optionsを使ったコマンドラインオプションのパース”

コメントを残す