Como definir comandos, opções e argumentos em System.CommandLine

Importante

Atualmente, System.CommandLine está em VERSÃO PRÉVIA, e essa documentação é para a versão 2.0 beta 4. Algumas informações estão relacionadas a produtos de pré-lançamento que poderão ser substancialmente modificados antes do lançamento. A Microsoft não oferece garantias, expressas ou implícitas, das informações aqui fornecidas.

Este artigo explica como definir comandos, opções e argumentos em aplicativos de linha de comando criados com a biblioteca System.CommandLine. Para criar um aplicativo completo que ilustra essas técnicas, confira o tutorial Introdução a System.CommandLine.

Para obter diretrizes sobre como criar comandos, opções e argumentos de um aplicativo de linha de comando, confira as Diretrizes de design.

Definir um comando raiz

Cada aplicativo de linha de comando tem um comando raiz, que se refere ao próprio arquivo executável. O caso mais simples para invocar seu código, se você tiver um aplicativo sem subcomandos, opções ou argumentos, terá esta aparência:

using System.CommandLine;

class Program
{
    static async Task Main(string[] args)
    {
        var rootCommand = new RootCommand("Sample command-line app");

        rootCommand.SetHandler(() =>
        {
            Console.WriteLine("Hello world!");
        });

        await rootCommand.InvokeAsync(args);
    }
}

Definir subcomandos

Os comandos podem ter comandos filhos, conhecidos como subcomandos ou verbos, e eles podem aninhar quantos níveis você precisar. Você pode adicionar subcomandos como mostrado neste exemplo:

var rootCommand = new RootCommand();
var sub1Command = new Command("sub1", "First-level subcommand");
rootCommand.Add(sub1Command);
var sub1aCommand = new Command("sub1a", "Second level subcommand");
sub1Command.Add(sub1aCommand);

O subcomando mais interno neste exemplo pode ser invocado da seguinte maneira:

myapp sub1 sub1a

Definir opções

Um método de manipulador de comando normalmente tem parâmetros e os valores podem vir de opções de linha de comando. O exemplo a seguir cria duas opções e as adiciona ao comando raiz. Os nomes de opção incluem prefixos de hifen duplo, que é típico para CLIs POSIX. O código do manipulador de comando exibe os valores dessas opções:

var delayOption = new Option<int>
    (name: "--delay",
    description: "An option whose argument is parsed as an int.",
    getDefaultValue: () => 42);
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayOption);
rootCommand.Add(messageOption);

rootCommand.SetHandler((delayOptionValue, messageOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
        Console.WriteLine($"--message = {messageOptionValue}");
    },
    delayOption, messageOption);

Aqui está um exemplo de entrada de linha de comando e a saída resultante do código de exemplo anterior:

myapp --delay 21 --message "Hello world!"
--delay = 21
--message = Hello world!

Opções globais

Para adicionar uma opção a um comando por vez, use o método Add ou AddOption, conforme mostrado no exemplo anterior. Para adicionar uma opção a um comando e recursivamente a todos os seus subcomandos, use o método AddGlobalOption, conforme mostrado no exemplo a seguir:

var delayOption = new Option<int>
    ("--delay", "An option whose argument is parsed as an int.");
var messageOption = new Option<string>
    ("--message", "An option whose argument is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.AddGlobalOption(delayOption);
rootCommand.Add(messageOption);

var subCommand1 = new Command("sub1", "First level subcommand");
rootCommand.Add(subCommand1);

var subCommand1a = new Command("sub1a", "Second level subcommand");
subCommand1.Add(subCommand1a);

subCommand1a.SetHandler((delayOptionValue) =>
    {
        Console.WriteLine($"--delay = {delayOptionValue}");
    },
    delayOption);

await rootCommand.InvokeAsync(args);

O código anterior adiciona --delay como uma opção global ao comando raiz e está disponível no manipulador para subCommand1a.

Definir argumentos

Os argumentos são definidos e adicionados aos comandos como opções. O exemplo a seguir é como o exemplo de opções, mas define argumentos em vez de opções:

var delayArgument = new Argument<int>
    (name: "delay",
    description: "An argument that is parsed as an int.",
    getDefaultValue: () => 42);
var messageArgument = new Argument<string>
    ("message", "An argument that is parsed as a string.");

var rootCommand = new RootCommand();
rootCommand.Add(delayArgument);
rootCommand.Add(messageArgument);

rootCommand.SetHandler((delayArgumentValue, messageArgumentValue) =>
    {
        Console.WriteLine($"<delay> argument = {delayArgumentValue}");
        Console.WriteLine($"<message> argument = {messageArgumentValue}");
    },
    delayArgument, messageArgument);

await rootCommand.InvokeAsync(args);

Aqui está um exemplo de entrada de linha de comando e a saída resultante do código de exemplo anterior:

myapp 42 "Hello world!"
<delay> argument = 42
<message> argument = Hello world!

Um argumento definido sem um valor padrão, como messageArgument no exemplo anterior, é tratado como um argumento obrigatório. Uma mensagem de erro é exibida, e o manipulador de comandos não é chamado, se um argumento obrigatório não é fornecido.

Definir aliases

Comandos e opções dão suporte para aliases. Você pode adicionar um alias a uma opção chamando AddAlias:

var option = new Option("--framework");
option.AddAlias("-f");

Com esse alias, as seguintes linhas de comando são equivalentes:

myapp -f net6.0
myapp --framework net6.0

Os aliases de comando funcionam da mesma maneira.

var command = new Command("serialize");
command.AddAlias("serialise");

Esse código torna as seguintes linhas de comando equivalentes:

myapp serialize
myapp serialise

Recomendamos que você minimize o número de aliases de opção definidos e evite definir determinados aliases específicos. Para obter mais informações, confira Aliases de forma abreviada.

Opções obrigatórias

Para tornar uma opção obrigatória, defina a propriedade IsRequired como true, conforme mostrado no exemplo a seguir:

var endpointOption = new Option<Uri>("--endpoint") { IsRequired = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

A seção de opções da ajuda de comando indica que a opção é obrigatória:

Options:
  --endpoint <uri> (REQUIRED)
  --version               Show version information
  -?, -h, --help          Show help and usage information

Se a linha de comando do aplicativo de exemplo não incluir --endpoint, uma mensagem de erro será exibida e o manipulador de comando não será chamado:

Option '--endpoint' is required.

Se uma opção obrigatória tiver um valor padrão, ela não precisará ser especificada na linha de comando. Nesse caso, o valor padrão fornecerá o valor da opção obrigatória.

Comandos, opções e argumentos ocultos

Talvez você queira dar suporte a um comando, opção ou argumento, mas evitar facilitar a descoberta. Por exemplo, pode ser um recurso preterido, administrativo ou de versão prévia. Use a propriedade IsHidden para evitar que os usuários descubram esses recursos usando a conclusão da guia ou a ajuda, conforme mostrado no exemplo a seguir:

var endpointOption = new Option<Uri>("--endpoint") { IsHidden = true };
var command = new RootCommand();
command.Add(endpointOption);

command.SetHandler((uri) =>
    {
        Console.WriteLine(uri?.GetType());
        Console.WriteLine(uri?.ToString());
    },
    endpointOption);

await command.InvokeAsync(args);

A seção de opções do comando deste exemplo ajuda a omitir a opção --endpoint.

Options:
  --version               Show version information
  -?, -h, --help          Show help and usage information

Definir arity de argumento

Você pode definir explicitamente a arity do argumento usando a propriedade Arity. Mas, na maioria dos casos, isso não é necessário. System.CommandLine determina automaticamente a arity do argumento com base no tipo dele:

Tipo de argumento Arity padrão
Boolean ArgumentArity.ZeroOrOne
Tipos de coleção ArgumentArity.ZeroOrMore
Todo o resto ArgumentArity.ExactlyOne

Vários argumentos

Por padrão, ao chamar um comando, você pode repetir um nome de opção para especificar vários argumentos para uma opção que tenha arity máxima maior que um.

myapp --items one --items two --items three

Para permitir vários argumentos sem repetir o nome da opção, defina Option.AllowMultipleArgumentsPerToken como true. Essa configuração permite que você insira a linha de comando a seguir.

myapp --items one two three

A mesma configuração terá um efeito diferente se a arity máxima do argumento for 1. Ela permite que você repita uma opção, mas usa apenas o último valor na linha. No exemplo a seguir, o valor three seria passado para o aplicativo.

myapp --item one --item two --item three

Listar valores de argumento válidos

Para especificar uma lista de valores válidos para uma opção ou argumento, especifique uma enumeração como o tipo de opção ou use FromAmong, conforme mostrado no exemplo a seguir:

var languageOption = new Option<string>(
    "--language",
    "An option that that must be one of the values of a static list.")
        .FromAmong(
            "csharp",
            "fsharp",
            "vb",
            "pwsh",
            "sql");

Aqui está um exemplo de entrada de linha de comando e a saída resultante do código de exemplo anterior:

myapp --language not-a-language
Argument 'not-a-language' not recognized. Must be one of:
        'csharp'
        'fsharp'
        'vb'
        'pwsh'
        'sql'

A seção de opções da ajuda de comando mostra os valores válidos:

Options:
  --language <csharp|fsharp|vb|pwsh|sql>  An option that must be one of the values of a static list.
  --version                               Show version information
  -?, -h, --help                          Show help and usage information

Validação de opção e argumento

Para obter informações sobre validação de argumento e como personalizá-la, confira as seguintes seções no artigo Associação de parâmetros:

Confira também