共用方式為


在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

此命令行會以使用者 apt update身分執行 admin 命令。

若要像這樣實作包裝函式命令,請將命令屬性 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並返回一個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 重載來取得 ParseResultCancellationToken,而不只是 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在調用期間傳遞給每個異步操作的,啟用進程終止訊號的處理(+SIGINTSIGTERMCancellationToken)。 默認會啟用它 (2 秒),但您可以將它設定為 null 停用它。

啟用時,如果動作未在指定的逾時內完成,進程將會終止。 在終止進程之前儲存狀態等措施,有助於優雅地處理終止過程。

若要測試上一段的範例程式代碼,請使用URL執行命令,該URL需要一些時間才能載入,然後在載入完成之前,按 Ctrl+C。 在macOS上,按 Command+句號(.)。 例如:

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概述