Dela via


Parsning och anrop i System.CommandLine

Viktigt!

System.CommandLine är för närvarande i förhandsversion. Den här dokumentationen gäller version 2.0 beta 7. Viss information gäller förhandsversionsprodukt som kan ändras avsevärt innan den släpps. Microsoft lämnar inga garantier, uttryckliga eller underförstådda, med avseende på den information som tillhandahålls här.

System.CommandLine ger en tydlig separation mellan kommandoradsparsing och åtgärdsanrop. Parsningsprocessen ansvarar för att parsa kommandoradsindata och skapa ett ParseResult objekt som innehåller de parsade värdena (och parsa fel). Åtgärdsanropsprocessen ansvarar för att anropa åtgärden som är associerad med det tolkade kommandot, alternativet eller direktivet (argument kan inte ha åtgärder).

I följande exempel från självstudiekursen Kom igång med System.CommandLine skapas genom att kommandoradsindata parsas.ParseResult Inga åtgärder definieras eller anropas:

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);
        }
    }
}

En åtgärd anropas när ett visst kommando (eller direktiv eller alternativ) parsas. Åtgärden är ett ombud som tar ett ParseResult argument och returnerar en int slutkod (asynkrona åtgärder är också tillgängliga). Slutkoden returneras av System.CommandLine.Parsing.ParseResult.Invoke metoden och kan användas för att ange om kommandot kördes korrekt eller inte.

I följande exempel från självstudien Kom igång med System.CommandLine definieras åtgärden för rotkommandot och anropas efter parsning av kommandoradsindata:

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);
        }
    }
}

Vissa inbyggda symboler, till exempel System.CommandLine.Help.HelpOption, System.CommandLine.VersionOptionoch System.CommandLine.Completions.SuggestDirective, levereras med fördefinierade åtgärder. Dessa symboler läggs automatiskt till i rotkommandot när du skapar det, och när du anropar System.CommandLine.Parsing.ParseResult, fungerar de "bara". Med hjälp av åtgärder kan du fokusera på din applogik, medan biblioteket tar hand om parsning och anrop av åtgärder för inbyggda symboler. Om du vill kan du hålla dig till parsningsprocessen och inte definiera några åtgärder (som i det första exemplet ovan).

ParseResult

Klassen ParseResult representerar resultatet av parsning av kommandoradsindata. Du måste använda den för att hämta parsade värden för alternativ och argument (oavsett om du använder åtgärder eller inte). Du kan också kontrollera om det fanns några parsningsfel eller omatchade token.

Hämta värde

Med ParseResult.GetValue metoden kan du hämta värdena för alternativ och argument:

int integer = parseResult.GetValue(delayOption);
string? message = parseResult.GetValue(messageOption);

Du kan också hämta värden efter namn, men detta kräver att du anger vilken typ av värde du vill hämta.

I följande exempel används C#-insamlingsinitierare för att skapa ett rotkommando:

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."
    }
};

Sedan använder den GetValue metoden för att hämta värdena efter namn:

rootCommand.SetAction(parseResult =>
{
    int integer = parseResult.GetValue<int>("--delay");
    string? message = parseResult.GetValue<string>("--message");

    DisplayIntAndString(integer, message);
});

Den här överlagringen av GetValue hämtar det parsade eller standardvärdet för det angivna symbolnamnet i kontexten för det parsade kommandot (inte hela symbolträdet). Den accepterar symbolnamnet, inte ett alias.

Tolkningsfel

Egenskapen ParseResult.Errors innehåller en lista över parsningsfel som inträffade under parsningsprocessen. Varje fel representeras av ett ParseError objekt som innehåller information om felet, till exempel felmeddelandet och token som orsakade felet.

När du anropar ParseResult.Invoke(InvocationConfiguration) metoden returneras en slutkod som anger om parsningen lyckades eller inte. Om det fanns några parsningsfel är slutkoden inte noll och alla parsningsfel skrivs ut till standardfelet.

Om du inte anropar ParseResult.Invoke metoden måste du hantera felen på egen hand, till exempel genom att skriva ut dem:

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

Omatchade token

Egenskapen UnmatchedTokens innehåller en lista över de token som parsades men inte matchade något konfigurerat kommando, alternativ eller argument.

Listan över omatchade token är användbar i kommandon som fungerar som omslag. Ett omslutningskommando tar en uppsättning token och vidarebefordrar dem till ett annat kommando eller en annan app. Kommandot sudo i Linux är ett exempel. Det tar användarnamnet för att efterlikna, följt av ett kommandot som ska köras. Följande kommando kör apt update till exempel kommandot som användaren admin:

sudo -u admin apt update

Om du vill implementera ett omslutningskommando som det här anger du kommandoegenskapen System.CommandLine.Command.TreatUnmatchedTokensAsErrors till false. System.CommandLine.Parsing.ParseResult.UnmatchedTokens Sedan innehåller egenskapen alla argument som inte uttryckligen tillhör kommandot. I föregående exempel ParseResult.UnmatchedTokens skulle innehålla apt token och update .

Åtgärder

Åtgärder är delegater som anropas när ett kommando (eller ett alternativ eller ett direktiv) parsas korrekt. De tar ett ParseResult argument och returnerar en int (eller Task<int>) slutkod. Slutkoden används för att ange om åtgärden har körts korrekt eller inte.

System.CommandLine innehåller en abstrakt basklass CommandLineAction och två härledda klasser: SynchronousCommandLineAction och AsynchronousCommandLineAction. Den förra används för synkrona åtgärder som returnerar en int slutkod, medan den senare används för asynkrona åtgärder som returnerar en Task<int> slutkod.

Du behöver inte skapa en härledd typ för att definiera en åtgärd. Du kan använda SetAction metoden för att ange en åtgärd för ett kommando. Den synkrona åtgärden kan vara ett ombud som tar ett ParseResult argument och returnerar en int slutkod. Den asynkrona åtgärden kan vara ett ombud som tar ParseResult och CancellationToken argument och returnerar en Task<int>.

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

Asynkrona åtgärder

Synkrona och asynkrona åtgärder bör inte blandas i samma program. Om du vill använda asynkrona åtgärder måste programmet vara asynkront hela. Det innebär att alla åtgärder ska vara asynkrona och du bör använda metoden System.CommandLine.Command.SetAction som accepterar att ett ombud returnerar en Task<int> slutkod. Dessutom måste det CancellationToken som skickas till åtgärdsdelegaten skickas vidare till alla metoder som kan avbrytas, till exempel fil-I/O-åtgärder eller nätverksbegäranden.

Du måste också se till att ParseResult.InvokeAsync(InvocationConfiguration, CancellationToken) metoden används i stället för Invoke. Den här metoden är asynkron och returnerar en Task<int> slutkod. Den accepterar också en valfri CancellationToken parameter som kan användas för att avbryta åtgärden.

Följande kod använder en SetAction överlagring som hämtar en ParseResult och en CancellationToken i stället för bara 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;
    }
}

Tidsgräns för processavslut

ProcessTerminationTimeout aktiverar signalering och hantering av processavslut (Ctrl+C, SIGINT, SIGTERM) via en CancellationToken som skickas till varje asynkron åtgärd under anropet. Den är aktiverad som standard (2 sekunder), men du kan ställa in den på null för att inaktivera den.

Om åtgärden inte slutförs inom den angivna tidsgränsen avslutas processen när den är aktiverad. Detta är användbart för att hantera avslutningen på ett korrekt sätt, till exempel genom att spara tillståndet innan processen avslutas.

Om du vill testa exempelkoden från föregående stycke kör du kommandot med en URL som tar en stund att läsa in och tryck på Ctrl+C innan inläsningen är klar. På macOS trycker du på Kommandoperiod+(.). Till exempel:

testapp --url https://free.blessedness.top/aspnet/core/fundamentals/minimal-apis
The operation was aborted

Utgångskoder

Slutkoden är ett heltalsvärde som returneras av en åtgärd som anger att den lyckades eller misslyckades. Enligt konventionen betyder en slutkod 0 för lyckades, medan alla värden som inte är noll indikerar ett fel. Det är viktigt att definiera meningsfulla utgångskoder i ditt program för att tydligt kommunicera statusen för kommandokörningen.

Varje SetAction metod har en överlagring som accepterar ett ombud som returnerar en int slutkod där slutkoden måste anges på ett explicit sätt och en överlagring som returnerar 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();
}

Se även