System.CommandLine は、コマンド ライン解析とアクション呼び出しの明確な分離を提供します。 解析プロセスは、コマンド ライン入力を解析し、解析された値 (および解析エラー) を含むParseResult オブジェクトを作成します。 アクション呼び出しプロセスは、解析されたコマンド、オプション、またはディレクティブに関連付けられているアクションを呼び出す役割を担います (引数にアクションを含めることはできません)。
System.CommandLineの概要チュートリアルの次の例では、コマンド ライン入力を解析してParseResultを作成します。 アクションは定義または呼び出されません。
using System.CommandLine;
using System.CommandLine.Parsing;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
rootCommand.Options.Add(fileOption);
ParseResult parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile)
{
ReadFile(parsedFile);
return 0;
}
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
}
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
}
特定のコマンド (またはディレクティブ、またはオプション) が正常に解析されると、アクションが呼び出されます。 アクションは、 ParseResult 引数を受け取り、 int 終了コードを返すデリゲートです (非同期アクション も使用できます)。 終了コードは ParseResult.Invoke(InvocationConfiguration) メソッドによって返され、コマンドが正常に実行されたかどうかを示すために使用できます。
System.CommandLineの概要チュートリアルの次の例では、アクションはルート コマンドに対して定義され、コマンド ライン入力の解析後に呼び出されます。
using System.CommandLine;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
rootCommand.Options.Add(fileOption);
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return 0;
});
ParseResult parseResult = rootCommand.Parse(args);
return parseResult.Invoke();
}
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
}
HelpOption、VersionOption、SuggestDirectiveなどの一部の組み込みシンボルには、定義済みのアクションが付属しています。 これらのシンボルは、ルート コマンドの作成時に自動的に追加され、 ParseResultを呼び出すと"動作"します。アクションを使用すると、アプリ ロジックに集中できます。一方、ライブラリでは組み込みシンボルのアクションの解析と呼び出しが行われます。 必要に応じて、解析プロセスにこだわり、アクションを定義することはできません (この記事の最初の例のように)。
ParseResult
ParseResult クラスは、コマンド ライン入力の解析結果を表します。 これを使用して、(アクションを使用しているかどうかに関係なく) オプションと引数の解析された値を取得する必要があります。 解析エラーや一致しない トークンがあるかどうかを確認することもできます。
値を取得
ParseResult.GetValue メソッドを使用すると、オプションと引数の値を取得できます。
int integer = parseResult.GetValue(delayOption);
string? message = parseResult.GetValue(messageOption);
名前で値を取得することもできますが、取得する値の型を指定する必要があります。
次の例では、C# コレクション初期化子を使用してルート コマンドを作成します。
RootCommand rootCommand = new("Parameter binding example")
{
new Option<int>("--delay")
{
Description = "An option whose argument is parsed as an int."
},
new Option<string>("--message")
{
Description = "An option whose argument is parsed as a string."
}
};
次に、 GetValue メソッドを使用して、名前で値を取得します。
rootCommand.SetAction(parseResult =>
{
int integer = parseResult.GetValue<int>("--delay");
string? message = parseResult.GetValue<string>("--message");
DisplayIntAndString(integer, message);
});
GetValueのこのオーバーロードは、解析されたコマンドのコンテキスト (シンボル ツリー全体ではなく) で、指定されたシンボル名の解析済みまたは既定値を取得します。
エイリアスではなくシンボル名を受け入れます。
解析エラー
ParseResult.Errors プロパティには、解析プロセス中に発生した解析エラーの一覧が含まれています。 各エラーは、エラー メッセージやエラーの原因となったトークンなど、エラーに関する情報を含む ParseError オブジェクトによって表されます。
ParseResult.Invoke(InvocationConfiguration) メソッドを呼び出すと、解析が成功したかどうかを示す終了コードが返されます。 解析エラーが発生した場合、終了コードは 0 以外であり、すべての解析エラーが標準エラーに出力されます。
ParseResult.Invoke メソッドを呼び出さない場合は、エラーを印刷するなどして、独自に処理する必要があります。
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
一致しないトークン
UnmatchedTokens プロパティには、解析されたが、構成されているコマンド、オプション、または引数と一致しなかったトークンの一覧が含まれています。
一致しないトークンの一覧は、ラッパーのように動作するコマンドで役立ちます。 ラッパー コマンドは、トークンのセット を 受け取り、別のコマンドまたはアプリに転送します。 Linux の sudo コマンドの例を示します。 偽装するユーザーの名前を受け取った後、次に実行するコマンドを入力します。 たとえば、次のコマンドは、ユーザー apt updateとして admin コマンドを実行します。
sudo -u admin apt update
このようなラッパー コマンドを実装するには、コマンド プロパティ System.CommandLine.Command.TreatUnmatchedTokensAsErrors を false に設定します。 その後、 System.CommandLine.Parsing.ParseResult.UnmatchedTokens プロパティには、コマンドに明示的に属していないすべての引数が含まれます。 前の例では、 ParseResult.UnmatchedTokens には apt トークンと update トークンが含まれています。
アクション
アクションは、コマンド (またはオプションまたはディレクティブ) が正常に解析されたときに呼び出されるデリゲートです。
ParseResult引数を受け取り、int (またはTask<int>) 終了コードを返します。 終了コードは、アクションが正常に実行されたかどうかを示すために使用されます。
System.CommandLine には、抽象基底クラス CommandLineAction と 2 つの派生クラス ( SynchronousCommandLineAction と AsynchronousCommandLineAction) が用意されています。 前者は int 終了コードを返す同期アクションに使用され、後者は Task<int> 終了コードを返す非同期アクションに使用されます。
アクションを定義するために派生型を作成する必要はありません。
SetAction メソッドを使用して、コマンドのアクションを設定できます。 同期アクションは、 ParseResult 引数を受け取り、 int 終了コードを返すデリゲートにすることができます。 非同期アクションは、 ParseResult 引数と CancellationToken 引数を受け取り、 Task<int>を返すデリゲートにすることができます。
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return 0;
});
非同期アクション
同期アクションと非同期アクションを同じアプリケーションで混在させるべきではありません。 非同期アクションを使用する場合は、アプリケーション全体を非同期にする必要があります。 つまり、すべてのアクションは非同期であり、SetAction終了コードを返すデリゲートを受け入れるTask<int> メソッドを使用する必要があります。 さらに、アクション デリゲートに渡される CancellationToken は、取り消すことができるすべてのメソッド (ファイル I/O 操作やネットワーク要求など) にさらに渡す必要があります。
また、ParseResult.InvokeAsync(InvocationConfiguration, CancellationToken)の代わりに Invoke メソッドが使用されるようにする必要もあります。 このメソッドは非同期であり、 Task<int> 終了コードを返します。 また、アクションの取り消しに使用できる省略可能な CancellationToken パラメーターも受け取ります。
次のコードでは、単なるSetActionではなく、ParseResult とCancellationTokenを取得するParseResult オーバーロードを使用します。
static Task<int> Main(string[] args)
{
Option<string> urlOption = new("--url")
{
Description = "A URL."
};
RootCommand rootCommand = new("Handle termination example") { urlOption };
rootCommand.SetAction((ParseResult parseResult, CancellationToken cancellationToken) =>
{
string? urlOptionValue = parseResult.GetValue(urlOption);
return DoRootCommand(urlOptionValue, cancellationToken);
});
return rootCommand.Parse(args).InvokeAsync();
}
public static async Task<int> DoRootCommand(
string? urlOptionValue, CancellationToken cancellationToken)
{
using HttpClient httpClient = new();
try
{
await httpClient.GetAsync(urlOptionValue, cancellationToken);
return 0;
}
catch (OperationCanceledException)
{
await Console.Error.WriteLineAsync("The operation was aborted");
return 1;
}
}
プロセス終了タイムアウト
ProcessTerminationTimeoutでは、呼び出し中にすべての非同期アクションに渡されるを介して、プロセス終了 (+SIGINT、SIGTERM、CancellationToken) のシグナル通知と処理が可能になります。 既定では (2 秒) 有効になっていますが、 null に設定して無効にすることができます。
有効にすると、指定したタイムアウト内にアクションが完了しない場合、プロセスは終了します。 これは、プロセスが終了する前の状態を保存するなどして、終了を適切に処理する場合に役立ちます。
前の段落のサンプル コードをテストするには、読み込みに少し時間がかかる URL でコマンドを実行し、読み込みが完了する前に Ctrl+キーを押します。 macOS で Command+Period(.)キーを押します。 例えば次が挙げられます。
testapp --url https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
The operation was aborted
終了コード
終了コードは、成功または失敗を示すアクションによって返される整数値です。 慣例により、 0 の終了コードは成功を示し、ゼロ以外の値はエラーを示します。 コマンド実行の状態を明確に伝えるために、アプリケーションで意味のある終了コードを定義することが重要です。
すべての SetAction メソッドには、終了コードを返すデリゲートを受け入れるオーバーロードと、明示的な方法で終了コードを指定する必要があるintと0を返すオーバーロードがあります。
static int Main(string[] args)
{
Option<int> delayOption = new("--delay");
Option<string> messageOption = new("--message");
RootCommand rootCommand = new("Parameter binding example")
{
delayOption,
messageOption
};
rootCommand.SetAction(parseResult =>
{
Console.WriteLine($"--delay = {parseResult.GetValue(delayOption)}");
Console.WriteLine($"--message = {parseResult.GetValue(messageOption)}");
// Value returned from the action delegate is the exit code.
return 100;
});
return rootCommand.Parse(args).Invoke();
}
こちらも参照ください
.NET