Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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 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 , viene ParseResult 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.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);
}
}
}
Un'azione viene richiamata quando un determinato comando (o direttiva o opzione) viene analizzato correttamente. L'azione è un delegato che accetta un ParseResult argomento 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 dell'esercitazione Introduzione System.CommandLine all'esercitazione, 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.VersionOptione 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
La ParseResult classe 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 ParseResult.GetValue 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 ParseResult.Errors proprietà contiene un elenco di errori di analisi che si sono verificati durante il processo di analisi. Ogni errore è rappresentato da un 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 ParseResult.Invoke(InvocationConfiguration) 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 chiama il 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 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. Ad esempio, il comando seguente esegue il apt update comando come utente admin:
sudo -u admin apt update
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 ParseResult argomento 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 CommandLineAction base astratta e due classi derivate: SynchronousCommandLineAction e 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 SetAction metodo per impostare un'azione per un comando. L'azione sincrona può essere un delegato che accetta un ParseResult argomento e restituisce un int codice di uscita. L'azione asincrona può essere un delegato che accetta ParseResult e CancellationToken argomenti 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 in tutto. 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.
È anche necessario assicurarsi che il ParseResult.InvokeAsync(InvocationConfiguration, CancellationToken) metodo venga usato invece di 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 seguente usa un SetAction overload che ottiene un parseResult e un anziché CancellationToken solo 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
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();
}