Tutorial: Introdução ao System.CommandLine
Importante
System.CommandLine
está atualmente em PRÉ-VISUALIZAÇÃO e esta documentação destina-se à versão 2.0 beta 4.
Algumas informações estão relacionadas com o produto de pré-lançamento que pode ser substancialmente modificado antes de ser lançado. A Microsoft não faz garantias, de forma expressa ou implícita, em relação à informação aqui apresentada.
Este tutorial mostra como criar uma aplicação de linha de comandos .NET que utiliza a System.CommandLine
biblioteca. Irá começar por criar um comando de raiz simples que tem uma opção. Em seguida, irá adicionar a essa base, criando uma aplicação mais complexa que contém vários subcomandos e diferentes opções para cada comando.
Neste tutorial, ficará a saber como:
- Criar comandos, opções e argumentos.
- Especifique os valores predefinidos para as opções.
- Atribua opções e argumentos a comandos.
- Atribua uma opção recursivamente a todos os subcomandos sob um comando.
- Trabalhe com vários níveis de subcomandos aninhados.
- Crie aliases para comandos e opções.
- Trabalhe com
string
os tipos de opção ,string[]
int
, ,bool
FileInfo
e enumeração. - Vincular valores de opção ao código do processador de comandos.
- Utilize código personalizado para analisar e validar opções.
Pré-requisitos
- Um editor de código, como o Visual Studio Code com a extensão C#.
- O SDK .NET 6.
Ou
- Visual Studio 2022 com a carga de trabalho de desenvolvimento de ambiente de trabalho .NET instalada.
Criar a aplicação
Crie um projeto de aplicação de consola .NET 6 com o nome "scl".
Crie uma pasta com o nome scl para o projeto e, em seguida, abra uma linha de comandos na nova pasta.
Execute o seguinte comando:
dotnet new console --framework net6.0
Instalar o pacote System.CommandLine
Execute o seguinte comando:
dotnet add package System.CommandLine --prerelease
A
--prerelease
opção é necessária porque a biblioteca ainda está em versão beta.
Substitua o conteúdo do ficheiro Program.cs pelo seguinte código:
using System.CommandLine; namespace scl; class Program { static async Task<int> Main(string[] args) { var fileOption = new Option<FileInfo?>( name: "--file", description: "The file to read and display on the console."); var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddOption(fileOption); rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption); return await rootCommand.InvokeAsync(args); } static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); } }
O código anterior:
Cria uma opção com o nome
--file
do tipo FileInfo e atribui-a ao comando raiz:var fileOption = new Option<FileInfo?>( name: "--file", description: "The file to read and display on the console."); var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddOption(fileOption);
Especifica que
ReadFile
é o método que será chamado quando o comando de raiz for invocado:rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption);
Apresenta o conteúdo do ficheiro especificado quando o comando raiz é invocado:
static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); }
Testar a aplicação
Pode utilizar qualquer uma das seguintes formas de testar ao desenvolver uma aplicação de linha de comandos:
Execute o
dotnet build
comando e, em seguida, abra uma linha de comandos na pasta scl/bin/Debug/net6.0 para executar o executável:dotnet build cd bin/Debug/net6.0 scl --file scl.runtimeconfig.json
Utilize
dotnet run
e transmita valores de opção para a aplicação em vez de para orun
comando ao incluí-los depois--
de , como no exemplo seguinte:dotnet run -- --file scl.runtimeconfig.json
Na Pré-visualização do SDK .NET 7.0.100, pode utilizar o
commandLineArgs
de um ficheiro launchSettings.json ao executar o comandodotnet run --launch-profile <profilename>
.Publique o projeto numa pasta, abra uma linha de comandos para essa pasta e execute o executável:
dotnet publish -o publish cd ./publish scl --file scl.runtimeconfig.json
No Visual Studio 2022, selecione DepurarPropriedades de Depuração> no menu e introduza as opções e argumentos na caixa Argumentos da linha de comandos. Por exemplo:
Em seguida, execute a aplicação, por exemplo, ao premir Ctrl+F5.
Este tutorial pressupõe que está a utilizar a primeira destas opções.
Quando executa a aplicação, esta apresenta o conteúdo do ficheiro especificado pela opção --file
.
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
Saída da ajuda
System.CommandLine
fornece automaticamente o resultado da ajuda:
scl --help
Description:
Sample app for System.CommandLine
Usage:
scl [options]
Options:
--file <file> The file to read and display on the console.
--version Show version information
-?, -h, --help Show help and usage information
Saída da versão
System.CommandLine
fornece automaticamente o resultado da versão:
scl --version
1.0.0
Adicionar um subcomando e opções
Nesta secção, pode:
- Crie mais opções.
- Crie um subcomando.
- Atribua as novas opções ao novo subcomando.
As novas opções permitir-lhe-ão configurar as cores do texto em primeiro plano e de fundo e a velocidade de leitura. Estas funcionalidades serão utilizadas para ler uma coleção de aspas provenientes do tutorial da aplicação de consola Teleprompter.
Copie o ficheiro sampleQuotes.txt do repositório do GitHub para este exemplo para o diretório do projeto. Para obter informações sobre como transferir ficheiros, consulte as instruções em Exemplos e Tutoriais.
Abra o ficheiro do projeto e adicione um
<ItemGroup>
elemento imediatamente antes da etiqueta de fecho</Project>
:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>
Adicionar esta marcação faz com que o ficheiro de texto seja copiado para a pasta bin/debug/net6.0 quando cria a aplicação. Por isso, quando executa o executável nessa pasta, pode aceder ao ficheiro por nome sem especificar um caminho de pasta.
Em Program.cs, após o código que cria a opção
--file
, crie opções para controlar a velocidade de leitura e as cores do texto:var delayOption = new Option<int>( name: "--delay", description: "Delay between lines, specified as milliseconds per character in a line.", getDefaultValue: () => 42); var fgcolorOption = new Option<ConsoleColor>( name: "--fgcolor", description: "Foreground color of text displayed on the console.", getDefaultValue: () => ConsoleColor.White); var lightModeOption = new Option<bool>( name: "--light-mode", description: "Background color of text displayed on the console: default is black, light mode is white.");
Depois da linha que cria o comando raiz, elimine a linha que adiciona a opção
--file
à mesma. Está a removê-lo aqui porque irá adicioná-lo a um novo subcomando.var rootCommand = new RootCommand("Sample app for System.CommandLine"); //rootCommand.AddOption(fileOption);
Depois da linha que cria o comando raiz, crie um
read
subcomando. Adicione as opções a este subcomando e adicione o subcomando ao comando raiz.var readCommand = new Command("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.AddCommand(readCommand);
Substitua o
SetHandler
código pelo seguinteSetHandler
código para o novo subcomando:readCommand.SetHandler(async (file, delay, fgcolor, lightMode) => { await ReadFile(file!, delay, fgcolor, lightMode); }, fileOption, delayOption, fgcolorOption, lightModeOption);
Já não está a chamar
SetHandler
no comando raiz porque o comando raiz já não precisa de um processador. Quando um comando tem subcomandos, normalmente tem de especificar um dos subcomandos ao invocar uma aplicação de linha de comandos.Substitua o método do
ReadFile
processador pelo seguinte código:internal static async Task ReadFile( FileInfo file, int delay, ConsoleColor fgColor, bool lightMode) { Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black; Console.ForegroundColor = fgColor; List<string> lines = File.ReadLines(file.FullName).ToList(); foreach (string line in lines) { Console.WriteLine(line); await Task.Delay(delay * line.Length); }; }
A aplicação tem agora o seguinte aspeto:
using System.CommandLine;
namespace scl;
class Program
{
static async Task<int> Main(string[] args)
{
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "The file to read and display on the console.");
var delayOption = new Option<int>(
name: "--delay",
description: "Delay between lines, specified as milliseconds per character in a line.",
getDefaultValue: () => 42);
var fgcolorOption = new Option<ConsoleColor>(
name: "--fgcolor",
description: "Foreground color of text displayed on the console.",
getDefaultValue: () => ConsoleColor.White);
var lightModeOption = new Option<bool>(
name: "--light-mode",
description: "Background color of text displayed on the console: default is black, light mode is white.");
var rootCommand = new RootCommand("Sample app for System.CommandLine");
//rootCommand.AddOption(fileOption);
var readCommand = new Command("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.AddCommand(readCommand);
readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
{
await ReadFile(file!, delay, fgcolor, lightMode);
},
fileOption, delayOption, fgcolorOption, lightModeOption);
return rootCommand.InvokeAsync(args).Result;
}
internal static async Task ReadFile(
FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
List<string> lines = File.ReadLines(file.FullName).ToList();
foreach (string line in lines)
{
Console.WriteLine(line);
await Task.Delay(delay * line.Length);
};
}
}
Testar o novo subcomando
Agora, se tentar executar a aplicação sem especificar o subcomando, receberá uma mensagem de erro seguida de uma mensagem de ajuda que especifica o subcomando que está disponível.
scl --file sampleQuotes.txt
'--file' was not matched. Did you mean one of the following?
--help
Required command was not provided.
Unrecognized command or argument '--file'.
Unrecognized command or argument 'sampleQuotes.txt'.
Description:
Sample app for System.CommandLine
Usage:
scl [command] [options]
Options:
--version Show version information
-?, -h, --help Show help and usage information
Commands:
read Read and display the file.
O texto de ajuda do subcomando read
mostra que estão disponíveis quatro opções. Mostra valores válidos para a enumeração.
scl read -h
Description:
Read and display the file.
Usage:
scl read [options]
Options:
--file <file> The file to read and display on the console.
--delay <delay> Delay between lines, specified as milliseconds per
character in a line. [default: 42]
--fgcolor Foreground color of text displayed on the console.
<Black|Blue|Cyan|DarkBlue|DarkCyan|DarkGray|DarkGreen|Dark [default: White]
Magenta|DarkRed|DarkYellow|Gray|Green|Magenta|Red|White|Ye
llow>
--light-mode Background color of text displayed on the console:
default is black, light mode is white.
-?, -h, --help Show help and usage information
Execute o subcomando read
especificando apenas a opção --file
e obtém os valores predefinidos para as outras três opções.
scl read --file sampleQuotes.txt
O atraso predefinido de 42 milissegundos por caráter causa uma velocidade de leitura lenta. Pode acelerá-lo ao definir --delay
um número mais baixo.
scl read --file sampleQuotes.txt --delay 0
Pode utilizar --fgcolor
e --light-mode
para definir cores de texto:
scl read --file sampleQuotes.txt --fgcolor red --light-mode
Forneça um valor inválido para --delay
e receberá uma mensagem de erro:
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
Forneça um valor inválido para --file
e obtém uma exceção:
scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.
Adicionar subcomandos e validação personalizada
Esta secção cria a versão final da aplicação. Quando terminar, a aplicação terá os seguintes comandos e opções:
- comando raiz com uma opção global* com o nome
--file
- Comando
quotes
read
com opções denominadas--delay
,--fgcolor
e--light-mode
add
comando com argumentos com o nomequote
ebyline
delete
comando com a opção com o nome--search-terms
- Comando
* Está disponível uma opção global para o comando ao qual está atribuído e recursivamente a todos os seus subcomandos.
Eis uma entrada de linha de comandos de exemplo que invoca cada um dos comandos disponíveis com as opções e argumentos:
scl quotes read --file sampleQuotes.txt --delay 40 --fgcolor red --light-mode
scl quotes add "Hello world!" "Nancy Davolio"
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Em Program.cs, substitua o código que cria a opção
--file
pelo seguinte código:var fileOption = new Option<FileInfo?>( name: "--file", description: "An option whose argument is parsed as a FileInfo", isDefault: true, parseArgument: result => { if (result.Tokens.Count == 0) { return new FileInfo("sampleQuotes.txt"); } string? filePath = result.Tokens.Single().Value; if (!File.Exists(filePath)) { result.ErrorMessage = "File does not exist"; return null; } else { return new FileInfo(filePath); } });
Este código utiliza ParseArgument<T> para fornecer análise personalizada, validação e processamento de erros.
Sem este código, os ficheiros em falta são reportados com uma exceção e rastreio de pilha. Com este código, é apresentada apenas a mensagem de erro especificada.
Este código também especifica um valor predefinido, razão pela qual é definido
isDefault
comotrue
. Se não estiver definidoisDefault
como , oparseArgument
delegado não será chamado quando não for fornecida nenhuma entrada para--file
true
.Depois do código que cria
lightModeOption
, adicione opções e argumentos para osadd
comandos edelete
:var searchTermsOption = new Option<string[]>( name: "--search-terms", description: "Strings to search for when deleting entries.") { IsRequired = true, AllowMultipleArgumentsPerToken = true }; var quoteArgument = new Argument<string>( name: "quote", description: "Text of quote."); var bylineArgument = new Argument<string>( name: "byline", description: "Byline of quote.");
A AllowMultipleArgumentsPerToken definição permite-lhe omitir o nome da opção
--search-terms
ao especificar elementos na lista após o primeiro. Torna os seguintes exemplos de entrada de linha de comandos equivalentes:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"
Substitua o código que cria o comando raiz e o
read
comando pelo seguinte código:var rootCommand = new RootCommand("Sample app for System.CommandLine"); rootCommand.AddGlobalOption(fileOption); var quotesCommand = new Command("quotes", "Work with a file that contains quotes."); rootCommand.AddCommand(quotesCommand); var readCommand = new Command("read", "Read and display the file.") { delayOption, fgcolorOption, lightModeOption }; quotesCommand.AddCommand(readCommand); var deleteCommand = new Command("delete", "Delete lines from the file."); deleteCommand.AddOption(searchTermsOption); quotesCommand.AddCommand(deleteCommand); var addCommand = new Command("add", "Add an entry to the file."); addCommand.AddArgument(quoteArgument); addCommand.AddArgument(bylineArgument); addCommand.AddAlias("insert"); quotesCommand.AddCommand(addCommand);
Este código efetua as seguintes alterações:
Remove a opção
--file
doread
comando .Adiciona a opção
--file
como uma opção global ao comando raiz.Cria um
quotes
comando e adiciona-o ao comando raiz.Adiciona o
read
comando aoquotes
comando em vez de ao comando raiz.add
Cria edelete
comandos e adiciona-os aoquotes
comando .
O resultado é a seguinte hierarquia de comandos:
- Comando raiz
quotes
read
add
delete
A aplicação implementa agora o padrão recomendado em que o comando principal (
quotes
) especifica uma área ou grupo e os respetivos comandos subordinados (read
,add
,delete
) são ações.As opções globais são aplicadas ao comando e recursivamente aos subcomandos. Uma
--file
vez que está no comando raiz, estará disponível automaticamente em todos os subcomandos da aplicação.Depois do
SetHandler
código, adicione um novoSetHandler
código para os novos subcomandos:deleteCommand.SetHandler((file, searchTerms) => { DeleteFromFile(file!, searchTerms); }, fileOption, searchTermsOption); addCommand.SetHandler((file, quote, byline) => { AddToFile(file!, quote, byline); }, fileOption, quoteArgument, bylineArgument);
O subcomando
quotes
não tem um processador porque não é um comando de folha. Os subcomandosread
,add
edelete
são comandos de folha em eSetHandler
são chamadosquotes
para cada um deles.Adicione os processadores para
add
edelete
.internal static void DeleteFromFile(FileInfo file, string[] searchTerms) { Console.WriteLine("Deleting from file"); File.WriteAllLines( file.FullName, File.ReadLines(file.FullName) .Where(line => searchTerms.All(s => !line.Contains(s))).ToList()); } internal static void AddToFile(FileInfo file, string quote, string byline) { Console.WriteLine("Adding to file"); using StreamWriter? writer = file.AppendText(); writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}"); writer.WriteLine($"{Environment.NewLine}-{byline}"); writer.Flush(); }
A aplicação concluída tem o seguinte aspeto:
using System.CommandLine;
namespace scl;
class Program
{
static async Task<int> Main(string[] args)
{
var fileOption = new Option<FileInfo?>(
name: "--file",
description: "An option whose argument is parsed as a FileInfo",
isDefault: true,
parseArgument: result =>
{
if (result.Tokens.Count == 0)
{
return new FileInfo("sampleQuotes.txt");
}
string? filePath = result.Tokens.Single().Value;
if (!File.Exists(filePath))
{
result.ErrorMessage = "File does not exist";
return null;
}
else
{
return new FileInfo(filePath);
}
});
var delayOption = new Option<int>(
name: "--delay",
description: "Delay between lines, specified as milliseconds per character in a line.",
getDefaultValue: () => 42);
var fgcolorOption = new Option<ConsoleColor>(
name: "--fgcolor",
description: "Foreground color of text displayed on the console.",
getDefaultValue: () => ConsoleColor.White);
var lightModeOption = new Option<bool>(
name: "--light-mode",
description: "Background color of text displayed on the console: default is black, light mode is white.");
var searchTermsOption = new Option<string[]>(
name: "--search-terms",
description: "Strings to search for when deleting entries.")
{ IsRequired = true, AllowMultipleArgumentsPerToken = true };
var quoteArgument = new Argument<string>(
name: "quote",
description: "Text of quote.");
var bylineArgument = new Argument<string>(
name: "byline",
description: "Byline of quote.");
var rootCommand = new RootCommand("Sample app for System.CommandLine");
rootCommand.AddGlobalOption(fileOption);
var quotesCommand = new Command("quotes", "Work with a file that contains quotes.");
rootCommand.AddCommand(quotesCommand);
var readCommand = new Command("read", "Read and display the file.")
{
delayOption,
fgcolorOption,
lightModeOption
};
quotesCommand.AddCommand(readCommand);
var deleteCommand = new Command("delete", "Delete lines from the file.");
deleteCommand.AddOption(searchTermsOption);
quotesCommand.AddCommand(deleteCommand);
var addCommand = new Command("add", "Add an entry to the file.");
addCommand.AddArgument(quoteArgument);
addCommand.AddArgument(bylineArgument);
addCommand.AddAlias("insert");
quotesCommand.AddCommand(addCommand);
readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
{
await ReadFile(file!, delay, fgcolor, lightMode);
},
fileOption, delayOption, fgcolorOption, lightModeOption);
deleteCommand.SetHandler((file, searchTerms) =>
{
DeleteFromFile(file!, searchTerms);
},
fileOption, searchTermsOption);
addCommand.SetHandler((file, quote, byline) =>
{
AddToFile(file!, quote, byline);
},
fileOption, quoteArgument, bylineArgument);
return await rootCommand.InvokeAsync(args);
}
internal static async Task ReadFile(
FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
var lines = File.ReadLines(file.FullName).ToList();
foreach (string line in lines)
{
Console.WriteLine(line);
await Task.Delay(delay * line.Length);
};
}
internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
{
Console.WriteLine("Deleting from file");
File.WriteAllLines(
file.FullName, File.ReadLines(file.FullName)
.Where(line => searchTerms.All(s => !line.Contains(s))).ToList());
}
internal static void AddToFile(FileInfo file, string quote, string byline)
{
Console.WriteLine("Adding to file");
using StreamWriter? writer = file.AppendText();
writer.WriteLine($"{Environment.NewLine}{Environment.NewLine}{quote}");
writer.WriteLine($"{Environment.NewLine}-{byline}");
writer.Flush();
}
}
Crie o projeto e, em seguida, experimente os seguintes comandos.
Submeta um ficheiro inexistente para --file
com o read
comando e receberá uma mensagem de erro em vez de uma exceção e rastreio de pilha:
scl quotes read --file nofile
File does not exist
Experimente executar o subcomando quotes
e recebe uma mensagem a direcioná-lo para utilizar read
, add
ou delete
:
scl quotes
Required command was not provided.
Description:
Work with a file that contains quotes.
Usage:
scl quotes [command] [options]
Options:
--file <file> An option whose argument is parsed as a FileInfo [default: sampleQuotes.txt]
-?, -h, --help Show help and usage information
Commands:
read Read and display the file.
delete Delete lines from the file.
add, insert <quote> <byline> Add an entry to the file.
Execute o subcomando add
e, em seguida, observe o fim do ficheiro de texto para ver o texto adicionado:
scl quotes add "Hello world!" "Nancy Davolio"
Execute o subcomando delete
com cadeias de pesquisa a partir do início do ficheiro e, em seguida, observe o início do ficheiro de texto para ver onde o texto foi removido:
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Nota
Se estiver a executar na pasta bin/debug/net6.0 , essa pasta é onde encontrará o ficheiro com as alterações dos add
comandos e delete
. A cópia do ficheiro na pasta do projeto permanece inalterada.
Passos seguintes
Neste tutorial, criou uma aplicação de linha de comandos simples que utiliza System.CommandLine
. Para saber mais sobre a biblioteca, consulte System.CommandLine Descrição geral.