重要
System.CommandLine
目前为预览版。 本文档适用于版本 2.0 beta 5。
某些信息与预发布产品有关,该产品可能在发布前进行大幅修改。 Microsoft对此处提供的信息不作任何明示或暗示的保证。
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.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
退出代码(也提供 异步操作)。 退出代码由 System.CommandLine.Parsing.ParseResult.Invoke
该方法返回,可用于指示命令是否已成功执行。
在 入门 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);
}
}
}
一些内置符号(例如 System.CommandLine.Help.HelpOption
,System.CommandLine.VersionOption
和 System.CommandLine.Completions.SuggestDirective
)附带预定义的动作。 这些符号会在您创建根命令以及调用System.CommandLine.Parsing.ParseResult
时自动添加,这样它们就能轻松运行。通过使用操作,您可以专注于应用程序逻辑,而库则负责解析和调用内置符号的操作。 如果您愿意,可以坚持进行解析过程,而不定义任何动作(如上面的第一个示例所示)。
ParseResult
该 ParseResult 类表示分析命令行输入的结果。 你需要使用它来获取选项和参数的解析值(无论是否使用操作)。 还可以检查是否存在任何解析错误或不匹配的令牌。
GetValue
此方法 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() 该方法时,它将返回一个退出代码,该代码指示分析是否成功。 如果存在任何分析错误,则退出代码为非零,所有分析错误都打印为标准错误。
如果不调用 ParseResult.Invoke
方法,则需要自己处理错误,例如通过打印它们:
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
不匹配的令牌
该 UnmatchedTokens 属性包含已分析但与任何配置的命令、选项或参数不匹配的令牌列表。
不匹配的令牌列表在行为类似于包装器的命令中非常有用。 包装命令采用一组令牌,并将其转发到另一个命令或应用程序。
sudo
Linux 中的命令是一个示例。 它采用要模拟的用户的名称,后接要运行的命令。 例如,以下命令以用户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 和两个派生类: SynchronousCommandLineAction 和 AsynchronousCommandLineAction。 前者用于返回 int
退出代码的同步操作,而后者用于返回 Task<int>
退出代码的异步操作。
无需创建派生类型来定义动作。 可以使用SetAction方法为命令设置动作。 同步操作可以是一个采用 ParseResult
参数,并返回 int
退出代码的委托。 异步操作可以是一个委托,该委托接受 ParseResult
和 CancellationToken 参数,并返回一个 Task<int>
。
rootCommand.SetAction(parseResult =>
{
FileInfo parsedFile = parseResult.GetValue(fileOption);
ReadFile(parsedFile);
return 0;
});
异步操作
同步和异步作不应在同一应用程序中混合。 如果要使用异步操作,应用程序需要始终是异步的。 这意味着所有操作都应是异步操作,你应使用接受委托并返回 System.CommandLine.Command.SetAction
退出代码的 Task<int>
方法。 此外,传递给操作委托的CancellationToken还需要进一步传递给所有可以取消的方法,例如文件 I/O 操作或网络请求。
还需要确保使用 ParseResult.InvokeAsync(CancellationToken) 方法而不是 Invoke
方法。 此方法是异步的,并返回 Task<int>
退出代码。 它还接受一个可选的 CancellationToken 参数,该参数可用于取消动作。
以下代码使用 SetAction
重载来获取 ParseResult 和 CancellationToken,而不仅仅是 ParseResult
:
static Task<int> Main(string[] args)
{
Option<string> urlOption = new("--url", "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通过在调用期间传递给每个异步操作的CancellationToken来启用信号和处理进程终止(Ctrl+C,SIGINT
SIGTERM
)。 它默认处于启用状态(2 秒),但你可以将其设置为 null
禁用它。
启用后,如果动作未在指定的超时时间内完成,则进程将被终止。 这可用于正常处理终止,例如,通过在进程终止之前保存状态。
若要测试上一段的示例代码,请使用 URL 运行命令,该 URL 需要一些时间才能加载完毕,然后在加载完成后按 Ctrl+C。 在 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();
}