.NETのコマンドラインパーサ System.CommandLine.DragonFruit の話

作成日:

C#に関するこのあたりのニュースを見ると、最近のC#は「Windows向けの巨大なGUIアプリやゲーム」だけではなく「クロスプラットフォームな軽量ツールやWebバックエンドアプリケーション」の需要を満たそうという意欲をかなり強く感じるし、その中でもGoがライバルであると見える。今のところそういった分野ではGoに相当水をあけられている(たぶん)が、実行速度がGoと同程度で、関数型言語寄りの機能も取り込んでおり、また同じ開発元で雰囲気も近いTypeScriptがWebフロントエンド屋さんに圧倒的人気を得ていることを考えるとワンチャンあるのでは? くらいの気持ちがある。

で、Goの得意なことをC#がどれほどできるのだろうと思い、まずはCLIツールに必須なコマンドラインパーサライブラリについて調べてみた。すると System.CommandLine.DragonFruit という公式お手軽コマンドラインパーサライブラリ(但しまだプレリリース)が出てきて面白かったので紹介する。

試す

プロジェクト作成とインストール

普通にdotnetコマンドでコンソールアプリケーションの雛型を生成したあと、dotnet addSystem.CommandLine.DragonFruitをNuGetから追加する。

$ mkdir DragonFruitSample
$ cd DragonFruitSample
$ dotnet new console
$ dotnet add package System.CommandLine.DragonFruit --prerelease

プログラムを書く

Program.csにこんなかんじに書く。

class Program
{
    /// <summary>
    /// サンプルプログラム
    /// </summary>
    /// <param name="input">繰り返したい文字列</param>
    /// <param name="times">繰り返し回数</param>
    static void Main(string input, uint times)
    {
        Console.WriteLine($"input is {input}, {times} times:");
        for(int i = 0; i < times; i++)
        {
            Console.WriteLine(input);
        }
    }
}

なんと、コマンドライン引数にしたいものをMain関数の引数にするだけ。

上のコメントはC#では昔からおなじみのXMLドキュメントコメント形式。なくても動くが、書いておくとヘルプに説明が出てくる。Visual Studioだと///と入れた時点で自動で雛型が入力されるので書くのも簡単1

実行する

dotnet runのあとにコマンドラインオプションを渡す場合は--を間に挟む。なお、オプションと引数の間はスペースでもイコールでもコロンでも良い2

$ dotnet run -- --input=大好き --times 5
input is 大好き, 5 times:
大好き
大好き
大好き
大好き
大好き

ヘルプは自動生成される。

$ dotnet run -- --help
Description:
  サンプルプログラム

Usage:
  DragonFruitSample [options]

Options:
  --input <input>  繰り返したい文字列
  --times <times>  繰り返し回数
  --version        Show version information
  -?, -h, --help   Show help and usage information

引数の型としてパースできない場合はエラーを吐いてくれる。

$ dotnet run -- --input=大好き --times="-1"
Cannot parse argument '-1' for option '--times' as expected type 'System.UInt32'.

Description:
  サンプルプログラム

Usage:
  DragonFruitSample [options]

Options:
  --input <input>  繰り返したい文字列
  --times <times>  繰り返し回数
  --version        Show version information
  -?, -h, --help   Show help and usage information

もちろん、dotnet run経由でなく実行ファイルを直接実行した場合も動く。

# Unix系の場合
$ cd bin/Debug/net6.0
$ ./DragonFruitSample --input=大好き --times 5
input is 大好き, 5 times:
大好き
大好き
大好き
大好き
大好き

備考

エントリポイントについて

エントリー ポイント - C# によるプログラミング入門 | ++C++; // 未確認飛行 C を参照すれば分かるとおり、本来C#のエントリポイントとなるMain関数には「引数はなしか、string[] のどちらか」という制限がある。従って、このSystem.CommandLine.DragonFruit向けのプログラムのMainはエントリポイントの条件を満たさない。

.NET - System.CommandLine を使用してコマンド ラインを解析する | Microsoft Docsには

お気づきのように、System.CommandLine では、–input オプションと –output オプションを Main のパラメーターに自動変換できるようになり、標準の Main(string[] args) エントリ ポイントを記述する必要もなくなりました。唯一追加された要件は、このシナリオを有効にするアセンブリを参照することです。何を参照するかについての詳細は、itl.tc/syscmddf で確認できます。なお、ここで説明されている内容は、アセンブリが NuGet でリリースされ次第、最新の情報ではなくなる可能性があります(残念ながら、これをサポートするための言語変更オプションはありません。ただし、参照を追加する際には、プロジェクト ファイルが変更され、あるビルド タスクが追加されます。このタスクによって生成される標準の Main メソッドの本文では、リフレクションを使って “カスタム” のエントリ ポイントが呼び出されます)。

とあり、このSystem.CommandLine.DragonFruitを参照として追加したときに黒魔術を使ってMainがエントリポイントのように働くようにしているらしい。従って、System.CommandLine.DragonFruitが追加されていない状態で上記のようなプログラムをビルドしようとすると「ライブラリが足りない」ではなく「エントリポイントがない」というエラーを吐く。

オプションでない引数について

static void Main(int intOption = 42, string[] args = null)
{
    Console.WriteLine($"The value of intOption is: {intOption}");

    foreach (var arg in args)
    {
        Console.WriteLine(arg);
    }
}

のようにargsargumentargumentsという名前の引数をとるようにすると、オプションでない引数がとれる。

詳しくは command-line-api/DragonFruit-overview.md at main · dotnet/command-line-api を参照。

まとめ

使い方を調べる必要がないくらいに単純明快で、軽いCLIツールを作るには十分な機能という印象。他の言語も含め、ちょっとしたスクリプトやツールをつくるときにコマンドラインパーサの仕様を毎度調べるのは面倒だったので、とてもありがたい。正式リリースに期待。


  1. Visual Studio Codeでも “Format On Type” の設定をOnにすると自動入力される(ref. xml comments - XML Auto Commenting C# in Visual Studio Code - Stack Overflow)(今調べて知った)。 ↩︎

  2. 詳しい仕様は System.CommandLine のコマンド ライン構文の概要 | Microsoft Docs 参照。 ↩︎



© 神和電子 2017-2023