System.CommandLine でコマンド、オプション、引数を定義する方法

重要

System.CommandLine は現在プレビュー段階であり、このドキュメントはバージョン 2.0 beta 4 を対象としています。 一部の情報は、リリース前に大きく変更される可能性があるプレリリースされた製品に関するものです。 Microsoft は、ここに記載されている情報について、明示または黙示を問わず、一切保証しません。

この記事では、 ライブラリを使って構築したコマンドライン アプリでコマンドオプション引数を定義する方法について説明します。 これらの手法を示す完全なアプリケーションを構築する場合は、 の概要に関するチュートリアルを参照してください。

コマンドライン アプリのコマンド、オプション、引数の設計方法のガイダンスについては、設計ガイダンスのページを参照してください。

ルート コマンドを定義する

すべてのコマンドライン アプリにはルート コマンドがあります。これは実行可能ファイルそのものを指します。 サブコマンド、オプション、引数がないアプリの場合、コードを呼び出す最も簡単なケースは、次のようになります。

using System.CommandLine;

class Program
{
    static async Task Main(string[] args)
    {
        var rootCommand = new RootCommand("Sample command-line app");

        rootCommand.SetHandler(() =>
        {
            Console.WriteLine("Hello world!");
        });

        await rootCommand.InvokeAsync(args);
    }
}

サブコマンドを定義する

コマンドは "サブコマンド" または "動詞" と呼ばれる子コマンドを含めることができます。また、必要な数だけ入れ子にすることができます。 次の例のようにサブコマンドを追加できます。

var rootCommand = new RootCommand();
var sub1Command = new Command("sub1", "First-level subcommand");
rootCommand.Add(sub1Command);
var sub1aCommand = new Command("sub1a", "Second level subcommand");
sub1Command.Add(sub1aCommand);

この例の一番内側のサブコマンドは次のように呼び出すことができます。

myapp sub1 sub1a

オプションを定義する

通常、コマンド ハンドラー メソッドにはパラメーターがあります。その値はコマンドライン オプションから取得できます。 次の例では、2 つのオプションを作成し、ルート コマンドに追加しています。 オプション名には、POSIX 規約に従って、二重ハイフンのプレフィックスが付いています。 コマンド ハンドラー コードを使うと、これらのオプションの値を表示できます。

var delayOption = new Option<int>
    (name: "--delay",
    description: "An option whose argument is parsed as an int.",
    getDefaultValue: () => 42);
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);

rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
        Console.WriteLine($"--message = {messageOptionValue}");
    },
    delayOption, messageOption);

前述のサンプル コードに対するコマンドライン入力とその結果の出力の例を次に示します。

myapp --delay 21 --message "Hello world!"
--delay = 21
--message = Hello world!

グローバルなオプション

一度に 1 つのコマンドにオプションを追加するには、前の例に示すように Add または AddOption メソッドを使います。 あるコマンドにオプションを追加し、そのサブコマンドすべてに再帰的にオプションを追加するには、次の例に示すように AddGlobalOption メソッドを使います。

var delayOption = new Option<int>
    ("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.AddGlobalOption(delayOption);
rootCommand.Add(messageOption);

var subCommand1 = new Command("sub1", "First level subcommand");
rootCommand.Add(subCommand1);

var subCommand1a = new Command("sub1a", "Second level subcommand");
subCommand1.Add(subCommand1a);

subCommand1a.SetHandler((delayOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
    },
    delayOption);

await rootCommand.InvokeAsync(args);

前述のコードでは、ルート コマンドにグローバル オプションとして --delay を追加し、subCommand1a のハンドラーで使用できるようにしています。

引数を定義する

引数を定義して、オプションのようにコマンドに追加します。 次の例は、オプションの例と同じですが、オプションではなく引数を定義しています。

var delayArgument = new Argument<int>
    (name: "delay",
    description: "An argument that is parsed as an int.",
    getDefaultValue: () => 42);
var messageArgument = new Argument<string>
    ("message", "An argument that is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayArgument);
rootCommand.Add(messageArgument);

rootCommand.SetHandler((delayArgumentValue, messageArgumentValue) =>
    {
        Console.WriteLine($"<delay> argument = {delayArgumentValue}");
        Console.WriteLine($"<message> argument = {messageArgumentValue}");
    },
    delayArgument, messageArgument);

await rootCommand.InvokeAsync(args);

前述のサンプル コードに対するコマンドライン入力とその結果の出力の例を次に示します。

myapp 42 "Hello world!"
<delay> argument = 42
<message> argument = Hello world!

前述の例の messageArgument のように、既定値なしで定義した引数は、必須の引数として扱われます。 必須の引数を指定しない場合、エラー メッセージが表示され、コマンド ハンドラーは呼び出されません。

別名を定義する

コマンドとオプションの両方が別名をサポートしています。 AddAlias を呼び出すことで、オプションに別名を追加できます。

var option = new Option("--framework");
option.AddAlias("-f");

この別名を指定しているので、次のコマンドラインは同等のものです。

myapp -f net6.0
myapp --framework net6.0

コマンドの別名も同じように機能します。

var command = new Command("serialize");
command.AddAlias("serialise");

このコードにより、次のコマンド ラインは同等になります。

myapp serialize
myapp serialise

オプションの別名の定義は最小限に抑え、特に、特定の別名の定義は避けることをお勧めします。 詳細については、「短い形式の別名」を参照してください。

必須オプション

オプションを必須にするには、次の例に示すように、その IsRequired プロパティを true に設定します。

var endpointOption = new Option<Uri>("--endpoint") { IsRequired = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

コマンド ヘルプのオプション セクションに、このオプションが必須であることが表示されます。

Options:
  --endpoint <uri> (REQUIRED)
  --version               Show version information
  -?, -h, --help          Show help and usage information

このサンプル アプリのコマンド ラインに --endpoint が含まれていない場合は、エラー メッセージが表示され、コマンド ハンドラーは呼び出されません。

Option '--endpoint' is required.

必須オプションに既定値がある場合は、コマンド ラインでオプションを指定する必要はありません。 その場合、既定値によって必須オプションの値が提供されます。

非表示のコマンド、オプション、引数

あるコマンド、オプション、引数をサポートはしても、それが簡単に見つからないようにしたい場合があります。 たとえば、非推奨、管理用、プレビューの機能などです。 次の例に示すように、IsHidden プロパティを使うと、ユーザーはタブ補完やヘルプを使ってその機能を見つけられなくなります。

var endpointOption = new Option<Uri>("--endpoint") { IsHidden = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

この例のコマンド ヘルプのオプション セクションでは、--endpoint オプションが省略されます。

Options:
  --version               Show version information
  -?, -h, --help          Show help and usage information

引数のアリティ (個数) を設定する

Arity プロパティを使うことで引数のアリティを明示的に設定できますが、ほとんどの場合は必要ありません。 System.CommandLine により、引数の型に基づいて引数のアリティが自動的に決定されます。

引数の型 既定のアリティ
Boolean ArgumentArity.ZeroOrOne
コレクション型 ArgumentArity.ZeroOrMore
その他すべて ArgumentArity.ExactlyOne

複数の引数

既定では、コマンドを呼び出すときにオプション名を繰り返し指定することで、最大アリティが 1 より大きいオプションに対して複数の引数を指定できます。ことができます。

myapp --items one --items two --items three

オプション名を繰り返さずに複数の引数を指定するには、Option.AllowMultipleArgumentsPerTokentrue に設定します。 この設定を使うと、次のようなコマンド ラインを入力できます。

myapp --items one two three

同じ設定でも、最大引数アリティが 1 の場合は、効果が異なります。 この場合、オプションを繰り返しても、その行の最後の値のみを受け取ることができます。 次の例では、値 three がアプリに渡されます。

myapp --item one --item two --item three

有効な引数値を一覧表示する

オプションまたは引数の有効な値の一覧を指定するには、次の例に示すように、オプションの型として enum を指定するか、FromAmong を使います。

var languageOption = new Option<string>(
    "--language",
    "An option that that must be one of the values of a static list.")
        .FromAmong(
            "csharp",
            "fsharp",
            "vb",
            "pwsh",
            "sql");

前述のサンプル コードに対するコマンドライン入力とその結果の出力の例を次に示します。

myapp --language not-a-language
Argument 'not-a-language' not recognized. Must be one of:
        'csharp'
        'fsharp'
        'vb'
        'pwsh'
        'sql'

コマンド ヘルプのオプション セクションに、有効な値が表示されます。

Options:
  --language <csharp|fsharp|vb|pwsh|sql>  An option that that must be one of the values of a static list.
  --version                               Show version information
  -?, -h, --help                          Show help and usage information

オプションと引数の検証

引数の検証とそのカスタマイズ方法については、パラメーター バインドに関する記事の以下のセクションを参照してください。

こちらもご覧ください