共用方式為


在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.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 結束代碼的委派(您也可以使用異步動作 )。 結束代碼是由 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(解析結果)

類別 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(InvocationConfiguration) 方法時,它會傳回結束代碼,指出剖析是否成功。 如果有任何解析錯誤,結束代碼為非零,同時所有解析錯誤都會列印至標準錯誤。

如果您未呼叫 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 會包含 aptupdate 令牌。

行動

操作是在成功解析命令、選項或指示詞時被調用的委派。 他們會採用自 ParseResult 變數並傳 int 回 (或 Task<int>) 結束代碼。 結束代碼是用來指出動作是否成功執行。

System.CommandLine 提供抽象基類 CommandLineAction 和兩個衍生類別: SynchronousCommandLineActionAsynchronousCommandLineAction。 前者用於傳回結束代碼的 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(InvocationConfiguration, 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透過在調用期間傳遞至每個異步動作的 ,啟用進程終止的訊號和處理。+。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();
}

另請參閱