CLIを開発する際、引数やオプション、ヘルプの扱いは意外と面倒です。特に、ユーザーフレンドリーなCLIにするためには、わかりやすいヘルプの整備が欠かせません。
これらを一から実装するのは手間ですが、そこで役立つのが .NET 向けライブラリ「System.CommandLine」です。
今回は、このライブラリを使って簡単な計算ツール(加算・減算)を作りながら、主な使い方を紹介します。
System.CommandLineは現在プレビューです。最新情報は公式サイトをご確認ください。
前提条件
以下の環境で動作確認を行っています。
os | windows11(4cpu、16GB) |
.NET | 8.0.304 |
IDE | Visual Studio 2022 Community |
System.CommandLine | 2.0.0-beta4.22272.1 |
記事のコードは、解説をわかりやすくするために、エラーチェックなど一部の処理を省略しています。
作成するCLIの仕様
以下のように加算・減算の計算ができるCLIを作成します
Calc.exe add 5 2 # → 7
Calc.exe sub 5 2 # → 3
Calc.exe sub 5 2 -r # → -3 (reverseオプション付き)
また、以下の要件も満たします。
- 引数の数が正しくない、または数値でない場合にエラーメッセージを表示
- sub コマンドのみに –reverse / -r オプションを付与
用語の整理
System.CommandLineでは、CLIの入力をスペースで区切った単位(トークン)として解析します。以下の例ではsub以降がそれぞれトークンとなります。
Calc.exe sub 5 2 --reverse false
この場合の各トークンと役割は以下のとおりです
トークン | 名前 |
sub | サブコマンド |
5 | サブコマンドの引数 |
2 | サブコマンドの引数 |
–reverse | オプション |
false | オプションの引数 |
実装
ライブラリの追加
System.CommandLine は現在プレビュー版のため、NuGet パッケージマネージャで「Include prerelease」にチェックを入れてインストールしてください。
ルートコマンドの作成
まずはルートコマンドを作成します。この下にサブコマンドやオプションを追加していきます。
// Program.cs
using System.CommandLine;
using ImageChange.Utils;
// ルートコマンドを登録
var rootCommand = new RootCommand("");
// 実際に実行される処理を追加
rootCommand.SetHandler(() =>
{
Console.WriteLine("Hello, World!");
});
await rootCommand.InvokeAsync(args);
この状態で実行すると “Hello, World!” が表示されます。
以降、SetHandler の部分は不要になるため削除して構いません。
サブコマンドの作成
続いてaddとsubの2つのサブコマンドを作成します。
// Program.cs
var addCommand = new Command("add", "add numbers");
var subCommnad = new Command("sub", "subtract numbers");
// それぞれのサブコマンドをルートコマンドに登録する
rootCommand.Add(addCommand);
rootCommand.Add(subCommnad);
この時点ではまだサブコマンドを実行しても何も実行されません。
サブコマンドに対する引数の追加
// Program.cs
// 2つの数値を受け取るためintの配列を指定
var numberArguments = new Argument<int[]>(name: "numbers", description: "set two numbers")
// サブコマンドに対して登録する
addCommand.Add(numberArguments);
subCommnad.Add(numberArguments);
サブコマンドの処理の実装
// Program.cs
addCommand.SetHandler((numbers) =>
{
Console.WriteLine(numbers[0] + numbers[1]);
}, numberArguments);
subCommnad.SetHandler((numbers) =>
{
Console.WriteLine(numbers[0] - numbers[1]);
}, numberArguments);
これで 「Calc add 1 2」 や 「Calc sub 1 2」 が動作するようになります。
バリデーションの追加
引数の数や値が不正な場合、明示的にエラーを表示させたい場合は、カスタムバリデーションを実装します。
// Program.cs
var numberArguments = new Argument<int[]>(name: "numbers",
parse: args =>
{
(int[] result, args.ErrorMessage) = Validations.IsTwoNumbers(args.Tokens);
return result;
},
description: "set two numbers"
)
{
Arity = ArgumentArity.OneOrMore
};
4行目でカスタムバリデーションを追加しています。引数が正常の場合はその値を返します。エラーがあった場合はErrorMessageプロパティにエラーメッセージを設定することで、そのメッセージとヘルプをコンソールに表示してくれます。
12行目のArityの指定は必須ではありませんが、OneOrMoreを指定することで1つ以上の引数を必須にできます。引数が0個の場合は組み込みのエラーメッセージを返してくれるため、カスタムバリデーションで引数0個のチェックする必要がなくなります。
引数の型チェックなど組み込みの検証があります。しかし、引数に文字列を指定した場合にエラーとならずにintの配列に0に変換されて渡されてしまうため、カスタムバリデーションを作成しています。
カスタムバリデーションは以下のとおりです。
public static class Validations
{
private static int[] defaulNumbers = [0, 0];
public static (int[], string) IsTwoNumbers(IReadOnlyList<Token> tokens)
{
if (tokens.Count < 2)
{
return (defaulNumbers, $"one more number is required");
}
if (tokens.Count >= 3)
{
return (defaulNumbers, "too many arguments");
}
int[] numbers = new int[2];
int index = 0;
foreach (var token in tokens)
{
var isConverted = int.TryParse(token.Value, out numbers[index]);
if (!isConverted)
{
return (defaulNumbers, $"arguments should be number");
}
index++;
}
return (numbers, string.Empty);
}
}
ここまででCLIを実行してみましょう。まずは正常パターンです。

続いて引数を指定しない場合です。これは組み込みのエラーメッセージが表示されます。また合わせてヘルプ(使い方)も表示されます。このヘルプも一から作成する必要はありません。

数値以外を指定した場合です。

オプションの登録
sub コマンドにだけ –reverse(または -r)オプションを追加して、引く順番を逆にできるようにします。
// Program.cs
var reverseOption = new Option<bool>(name: "--reverse", description: "reverse the two numbers and then calculate them",
getDefaultValue: () => false);
// -rでも実行できるようにエイリアスを登録する
reverseOption.AddAlias("-r");
// 引き算のサブコマンドにだけ登録
subCommnad.Add(reverseOption);
// オプションを受け取り、その値に基づいて計算する
subCommnad.SetHandler((numbers, option) =>
{
var result = numbers[0] - numbers[1];
var reverse = option ? -1 : 1;
Console.WriteLine(result * reverse);
}, numberArguments, reverseOption);

なお引数とオプションは順不同です。

ただし、オプションをサブコマンドの前に指定すると、サブコマンドのオプションとは認識されなくなるためエラーとなります。

また、登録したオプション以外のオプションを指定すると、引数と解釈されます。

最後に
System.CommandLine を使えば、CLIツールに必要な機能(引数処理、バリデーション、ヘルプ、オプションなど)を簡潔かつ強力に実装できます。
プレビュー段階とはいえ、使い勝手が非常によく、今後も注目のライブラリです。
CLIを.NETで構築する際には、ぜひ活用してみてください。
コメント