分析与调用 System.CommandLine

重要

System.CommandLine 目前为预览版,本文档适用于版本 2.0 beta 5。 某些信息与预发布产品有关,该产品在发布前可能会进行大幅修改。 Microsoft对此处提供的信息不作任何明示或暗示的保证。

System.CommandLine 提供命令行分析和作调用之间的明确分隔。 分析过程负责分析命令行输入,并创建 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);
        }
    }
}

成功分析给定命令(或指令或选项)时,将调用作。 该作是一个委托,它采用参数 System.CommandLine.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.HelpOptionSystem.CommandLine.VersionOptionSystem.CommandLine.Completions.SuggestDirective)附带预定义的作。 这些符号会在创建根命令时自动添加到根命令,调用这些 System.CommandLine.Parsing.ParseResult符号时,它们“只需工作”。使用作可以专注于应用逻辑,而库负责分析和调用内置符号的作。 如果愿意,可以坚持分析过程,而不定义任何作(如上面的第一个示例所示)。

ParseResult

System.CommandLine.Parsing.ParseResult 类型是一个类,表示分析命令行输入的结果。 你需要使用它来获取选项和参数的已分析值(无论是否使用作)。 还可以检查是否存在任何分析错误或不匹配 的令牌

GetValue

此方法 System.CommandLine.Parsing.ParseResult.GetValue<T> 允许检索选项和参数的值:

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 在分析的命令(而不是整个符号树)的上下文中获取指定符号名称的已分析或默认值。 它接受符号名称,而不是 别名

分析错误

System.CommandLine.Parsing.ParseResult.Errors 属性包含分析过程中发生的分析错误列表。 每个错误都由一个 System.CommandLine.Parsing.ParseError 对象表示,该对象包含有关错误的信息,例如错误消息和导致错误的令牌。

调用 System.CommandLine.Parsing.ParseResult.Invoke 该方法时,它将返回一个退出代码,该代码指示分析是否成功。 如果存在任何分析错误,则退出代码为非零,所有分析错误都输出到标准错误。

如果不调用 System.CommandLine.Parsing.ParseResult.Invoke 该方法,则需要通过打印它们自行处理错误:

foreach (ParseError parseError in parseResult.Errors)
{
    Console.Error.WriteLine(parseError.Message);
}
return 1;

不匹配的令牌

System.CommandLine.Parsing.ParseResult.UnmatchedTokens 属性包含已分析但与任何配置的命令、选项或参数不匹配的令牌列表。

不匹配的令牌列表在行为类似于包装器的命令中非常有用。 包装器命令采用一组 令牌 ,并将其转发到另一个命令或应用。 sudo Linux 中的命令是一个示例。 它采用用户的名称来模拟后跟要运行的命令。 例如:

sudo -u admin apt update

此命令行将作为用户admin运行apt update命令。

若要实现如下所示的包装器命令,请将命令属性 System.CommandLine.Command.TreatUnmatchedTokensAsErrors 设置为 false。 然后,该 System.CommandLine.Parsing.ParseResult.UnmatchedTokens 属性将包含不显式属于该命令的所有参数。 在前面的示例中,ParseResult.UnmatchedTokens将包含和aptupdate标记。

行动

作是成功分析命令(或选项或指令)时调用的委托。 它们采用参数 System.CommandLine.ParseResult 并返回 int (或 Task<int>) 退出代码。 退出代码用于指示作是否已成功执行。

System.CommandLine 提供抽象基类 System.CommandLine.CommandLineAction 和两个派生类: System.CommandLine.SynchronousCommandLineActionSystem.CommandLine.AsynchronousCommandLineAction。 前者用于返回 int 退出代码的同步作,而后者用于返回退出代码的 Task<int> 异步作。

无需创建派生类型来定义作。 可以使用该方法 System.CommandLine.Command.SetAction 为命令设置作。 同步作可以是采用 System.CommandLine.ParseResult 参数并返回退出代码的 int 委托。 异步作可以是接受参数System.CommandLine.ParseResultCancellationToken返回 a.Task<int>

rootCommand.SetAction(parseResult =>
{
    FileInfo parsedFile = parseResult.GetValue(fileOption);
    ReadFile(parsedFile);
    return 0;
});

异步作

同步和异步作不应在同一应用程序中混合。 如果要使用异步作,应用程序需要从上到下异步。 这意味着所有作都应是异步的,应使用 System.CommandLine.Command.SetAction 接受返回 Task<int> 退出代码的委托的方法。 此外, CancellationToken 传递给作委托的作委托需要进一步传递给可取消的所有方法,例如文件 I/O作或网络请求。

除此之外,需要确保 System.CommandLine.Parsing.ParseResult.InvokeAsync 使用该方法而不是 System.CommandLine.Parsing.ParseResult.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;
    }
}

进程终止超时

System.CommandLine.CommandLineConfiguration.ProcessTerminationTimeout通过调用期间传递给每个异步作的信号和处理进程终止(Ctrl+CSIGINTSIGTERMCancellationToken 启用信号处理。 它默认处于启用状态(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();
}

另请参阅

如何在 中System.CommandLine自定义分析和验证System.CommandLine 概述