Condividi tramite


Analisi e invocazione nel System.CommandLine

Importante

System.CommandLine è attualmente disponibile in ANTEPRIMA e questa documentazione è per la versione 2.0 beta 5. Alcune informazioni riguardano il prodotto in fase di pre-rilascio che potrebbe essere modificato in modo sostanziale prima del rilascio. Microsoft non fornisce alcuna garanzia, espressa o implicita, in relazione alle informazioni fornite qui.

System.CommandLine offre una netta separazione tra l'analisi della riga di comando e la chiamata all'azione. Il processo di analisi è responsabile dell'analisi dell'input della riga di comando e della creazione di un System.CommandLine.ParseResult oggetto contenente i valori analizzati (e analizzare gli errori). Il processo di chiamata all'azione è responsabile della chiamata dell'azione associata al comando analizzato, all'opzione o alla direttiva (gli argomenti non possono avere azioni).

Nell'esempio seguente dell'esercitazione Introduzione System.CommandLine, ParseResult viene creato analizzando l'input della riga di comando. Non vengono definite o richiamate azioni:

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

Un'azione viene richiamata quando un determinato comando (o direttiva o opzione) viene analizzato correttamente. L'azione è un delegato che accetta un System.CommandLine.ParseResult parametro e restituisce un int codice di uscita (sono disponibili anche azioni asincrone). Il codice di uscita viene restituito dal System.CommandLine.Parsing.ParseResult.Invoke metodo e può essere usato per indicare se il comando è stato eseguito correttamente o meno.

Nell'esempio seguente tratto dal nostro tutorial Introduzione System.CommandLine, l'azione viene definita per il comando radice e richiamata dopo l'analisi dell'input della riga di comando.

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

Alcuni simboli predefiniti, ad esempio System.CommandLine.Help.HelpOption, System.CommandLine.VersionOptiono System.CommandLine.Completions.SuggestDirective, sono dotati di azioni predefinite. Questi simboli vengono aggiunti automaticamente al comando radice quando lo si crea e quando si richiama System.CommandLine.Parsing.ParseResult, essi "funzionano semplicemente". L'uso delle azioni consente di concentrarsi sulla logica dell'app, mentre la libreria si occupa dell'analisi e della chiamata di azioni per i simboli predefiniti. Se si preferisce, è possibile attenersi al processo di analisi e non definire alcuna azione (come nel primo esempio precedente).

ParseResult

Il System.CommandLine.Parsing.ParseResult tipo è una classe che rappresenta i risultati dell'analisi dell'input della riga di comando. È necessario usarlo per ottenere i valori analizzati per le opzioni e gli argomenti ,indipendentemente dal fatto che si usino o meno azioni. È anche possibile verificare se sono presenti errori di analisi o token non corrispondenti.

GetValue

Il System.CommandLine.Parsing.ParseResult.GetValue<T> metodo consente di recuperare i valori delle opzioni e degli argomenti:

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

È anche possibile ottenere valori in base al nome, ma è necessario specificare il tipo del valore che si vuole ottenere.

L'esempio seguente usa gli inizializzatori di raccolta C# per creare un comando radice:

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

Usa quindi il GetValue metodo per ottenere i valori in base al nome:

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

    DisplayIntAndString(integer, message);
});

Questo overload di GetValue ottiene il valore analizzato o predefinito per il nome del simbolo specificato, nel contesto del comando analizzato (non l'intero albero dei simboli). Accetta il nome del simbolo, non un alias.

Analizzare gli errori

La System.CommandLine.Parsing.ParseResult.Errors proprietà contiene un elenco di errori di analisi che si sono verificati durante il processo di analisi. Ogni errore è rappresentato da un System.CommandLine.Parsing.ParseError oggetto , che contiene informazioni sull'errore, ad esempio il messaggio di errore e il token che ha causato l'errore.

Quando si chiama il System.CommandLine.Parsing.ParseResult.Invoke metodo, restituisce un codice di uscita che indica se l'analisi è riuscita o meno. Se ci sono errori di parsing, il codice di uscita è diverso da zero e tutti gli errori di parsing vengono stampati nello standard error.

Se non si richiama il System.CommandLine.Parsing.ParseResult.Invoke metodo , è necessario gestire gli errori autonomamente, ad esempio stampandoli:

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

Token non corrispondenti

La System.CommandLine.Parsing.ParseResult.UnmatchedTokens proprietà contiene un elenco dei token analizzati ma che non corrispondono ad alcun comando, opzione o argomento configurato.

L'elenco di token non corrispondenti è utile nei comandi che si comportano come wrapper. Un comando wrapper accetta un set di token e li inoltra a un altro comando o app. Il sudo comando in Linux è un esempio. Richiede il nome di un utente da impersonare seguito da un comando da eseguire. Per esempio:

sudo -u admin apt update

Questa riga di comando eseguirà il apt update comando come utente admin.

Per implementare un comando wrapper come questo, impostare la proprietà System.CommandLine.Command.TreatUnmatchedTokensAsErrors del comando su false. La System.CommandLine.Parsing.ParseResult.UnmatchedTokens proprietà conterrà quindi tutti gli argomenti che non appartengono in modo esplicito al comando. Nell'esempio precedente, ParseResult.UnmatchedTokens conterrà i token apt e update.

Azioni

Le azioni sono delegati richiamati quando un comando (o un'opzione o una direttiva) viene analizzato correttamente. Accettano un System.CommandLine.ParseResult parametro e restituiscono un int codice di uscita (o Task<int>). Il codice di uscita viene usato per indicare se l'azione è stata eseguita correttamente o meno.

System.CommandLine fornisce una classe System.CommandLine.CommandLineAction base astratta e due classi derivate: System.CommandLine.SynchronousCommandLineAction e System.CommandLine.AsynchronousCommandLineAction. Il primo viene usato per le azioni sincrone che restituiscono un int codice di uscita, mentre quest'ultimo viene usato per azioni asincrone che restituiscono un Task<int> codice di uscita.

Non è necessario creare un tipo derivato per definire un'azione. È possibile usare il System.CommandLine.Command.SetAction metodo per impostare un'azione per un comando. L'azione sincrona può essere un delegato che accetta un System.CommandLine.ParseResult parametro e restituisce un int codice di uscita. L'azione asincrona può essere un delegato che accetta parametri System.CommandLine.ParseResult e CancellationToken e restituisce un oggetto Task<int>.

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

Azioni asincrone

Le azioni sincrone e asincrone non devono essere miste nella stessa applicazione. Se si vogliono usare azioni asincrone, l'applicazione deve essere asincrona dall'alto verso il basso. Ciò significa che tutte le azioni devono essere asincrone ed è necessario usare il System.CommandLine.Command.SetAction metodo che accetta un delegato che restituisce un Task<int> codice di uscita. Inoltre, l'oggetto CancellationToken passato al delegato di azione deve essere passato ulteriormente a tutti i metodi che possono essere annullati, ad esempio operazioni di I/O file o richieste di rete.

Oltre a questo, è necessario assicurarsi che il System.CommandLine.Parsing.ParseResult.InvokeAsync metodo venga usato invece di System.CommandLine.Parsing.ParseResult.Invoke. Questo metodo è asincrono e restituisce un Task<int> codice di uscita. Accetta anche un parametro facoltativo CancellationToken che può essere usato per annullare l'azione.

Il codice precedente usa un SetAction overload che ottiene un ParseResult e un CancellationToken anziché semplicemente 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;
    }
}

Timeout di terminazione del processo

System.CommandLine.CommandLineConfiguration.ProcessTerminationTimeout abilita la segnalazione e la gestione della terminazione del processo (CTRL+C, SIGINT, SIGTERM) tramite un CancellationToken oggetto passato a ogni azione asincrona durante la chiamata. È abilitato per impostazione predefinita (2 secondi), ma è possibile impostarlo su null per disabilitarlo.

Se abilitata, se l'azione non viene completata entro il timeout specificato, il processo verrà terminato. Ciò è utile per gestire correttamente la terminazione, ad esempio salvando lo stato prima che il processo venga terminato.

Per testare il codice di esempio del paragrafo precedente, eseguire il comando con un URL che richiederà un po' di tempo per il caricamento e prima di completare il caricamento, premere CTRL+C. Su macOS premere Comando++Punto (.). Per esempio:

testapp --url https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis
The operation was aborted

Codici di uscita

Il codice di uscita è un valore intero restituito da un'azione che indica l'esito positivo o negativo. Per convenzione, un codice di uscita di 0 indica l'esito positivo, mentre qualsiasi valore diverso da zero indica un errore. È importante definire codici di uscita significativi nell'applicazione per comunicare chiaramente lo stato dell'esecuzione dei comandi.

Ogni metodo SetAction ha un sovraccarico che accetta un delegato che restituisce un codice di uscita int dove il codice di uscita deve essere fornito in maniera esplicita e un sovraccarico che restituisce 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();
}

Vedere anche

Come personalizzare l'analisi e la convalida in System.CommandLineSystem.CommandLine panoramica