Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Dica
Este artigo faz parte da seção Conceitos Básicos , escrita para desenvolvedores que conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Se você não estiver familiarizado com a programação, comece com Introdução. Se você precisar de cobertura abrangente da biblioteca, consulte a documentação da biblioteca System.CommandLine.
A System.CommandLine biblioteca manipula a análise de linha de comando, a geração de texto de ajuda e a validação de entrada para que você possa se concentrar na lógica do aplicativo. Neste tutorial, você criará uma CLI do rastreador de tarefas que demonstra os principais conceitos: comandos, subcomandos, opções e argumentos.
Neste tutorial, você:
- Crie um aplicativo baseado em arquivo usando o
System.CommandLinepacote. - Defina opções e argumentos com valores tipados.
- Crie subcomandos e anexe opções e argumentos a eles.
- Execute cada subcomando com uma ação.
- Teste o aplicativo com entradas de linha de comando diferentes.
Pré-requisitos
- Instale o SDK do .NET 10 ou posterior.
Criar o aplicativo
Comece criando um programa C# baseado em arquivo e adicionando o System.CommandLine pacote.
Crie um arquivo nomeado
TaskCli.cscom o seguinte conteúdo:#!/usr/bin/env dotnetA linha shebang
#!permite executar o arquivo diretamente nos sistemas Unix. No Windows, execute o arquivo comdotnet run TaskCli.cs.Adicione a diretiva de pacote
System.CommandLinee as instruçõesusingnecessárias.#:package System.CommandLine@2.0.0using System.CommandLine; using System.CommandLine.Parsing; using System.Text.Json;Importante
A versão
2.0.0é a versão mais recente no momento da gravação. Verifique a página NuGet do pacote para obter a versão mais recente para garantir que você tenha as correções de segurança mais recentes.
Entender a estrutura de comandos
Antes de escrever qualquer código de análise, considere a aparência da CLI da perspectiva do usuário. O rastreador de tarefas dá suporte a quatro operações:
dotnet TaskCli.cs -- add "Write documentation" --priority High --due 2026-04-01
dotnet TaskCli.cs -- list --all
dotnet TaskCli.cs -- complete 3
dotnet TaskCli.cs -- remove 3
dotnet TaskCli.cs -- --verbose list
Observação
O -- após TaskCli.cs nos exemplos precedentes informa dotnet run que todos os argumentos restantes são enviados para o seu aplicativo, em vez de serem interpretados pela própria dotnet CLI.
Cada linha usa vários conceitos de linha de comando:
-
Subcomandos são verbos que dizem ao aplicativo o que fazer. O rastreador de tarefas tem quatro:
add,list, ecompleteremove. Cada subcomando pode definir seus próprios parâmetros. -
Argumentos são valores posicionais que seguem um subcomando. Em
add "Write documentation", a cadeia de caracteres"Write documentation"é um argumento que especifica a descrição da tarefa. Emcomplete 3, o número3é um argumento que especifica a ID da tarefa. -
As opções são valores nomeados prefixados com
--. Emadd --priority High --due 2026-04-01, ambos--prioritye--duesão opções com seus próprios valores. Emlist --all, a opção--allé um sinalizador booliano que não precisa de um valor. -
As opções globais se aplicam a cada subcomando. A
--verboseopção é definida no comando raiz comRecursive = true, portanto, funciona com qualquer subcomando. Em--verbose list, o flag verboso aparece antes do subcomando, maslist --verbosefunciona igualmente bem.
Nas seções a seguir, você cria essas peças de baixo para cima. Primeiro, você define as opções individuais (como --priority e --all) e argumentos (como a descrição e a ID da tarefa). Em seguida, crie os quatro subcomandos e anexe as opções e argumentos relevantes a cada um deles. Em seguida, você conecta uma ação para cada subcomando. A ação é o código executado quando o usuário invoca esse comando. Por fim, você monta o comando raiz, analisa a entrada e invoca a ação correspondente.
Para obter uma visão mais detalhada dos conceitos de sintaxe de linha de comando, consulte a visão geral da sintaxe de linha de comando.
Definir opções e argumentos
As opções representam valores nomeados que os usuários especificam com um -- prefixo. Os argumentos representam valores posicionais. Ambos são fortemente tipados.
System.CommandLine analisa a cadeia de caracteres de entrada no tipo especificado.
A System.CommandLine biblioteca usa tipos genéricos para garantir a segurança de tipos. Quando você escreve Option<int>, o int entre os colchetes angulares é um argumento de tipo. Ele informa à biblioteca qual tipo de valor a opção contém. A própria classe declara um parâmetroT de tipo (como em System.CommandLine.Option<T>) e você fornece o tipo concreto ao criar uma instância. A biblioteca analisa a cadeia de caracteres de entrada do usuário e a converte automaticamente nesse tipo. Se o usuário fornecer --delay abc para um Option<int>, System.CommandLine relatará um erro de análise em vez de passar dados incorretos para seu código. Você verá esse padrão com Option<bool>, Option<Priority>Option<DateOnly?>e System.CommandLine.Argument<T> nas etapas a seguir. Confira mais informações sobre genéricos em Genéricos.
Defina as opções. Cada
Option<T>um especifica o tipo de valor, o nome e uma descrição. A--priorityopção usa umenumtipo eSystem.CommandLinevalida automaticamente a entrada em relação aos valores válidos de enum:var verboseOption = new Option<bool>("--verbose") { Description = "Show detailed output", Recursive = true }; var priorityOption = new Option<Priority>("--priority") { Description = "Task priority level", DefaultValueFactory = _ => Priority.Medium }; var dueOption = new Option<DateOnly?>("--due") { Description = "Due date (uses current culture date format)" }; var allOption = new Option<bool>("--all") { Description = "Include completed tasks" };A
Recursive = trueconfiguração em--verbosetorna a opção disponível em cada subcomando. ODefaultValueFactoryon--priorityfornece um valor padrão para que os usuários possam omitir a opção.Defina os argumentos. Cada
Argument<T>um especifica o tipo de valor e um nome:var descriptionArgument = new Argument<string>("description") { Description = "Task description" }; var taskIdArgument = new Argument<int>("id") { Description = "Task ID" };
Criar comandos e subcomandos
System.CommandLine.Command representa uma ação que o usuário pode invocar. Adicione as opções e argumentos relevantes a cada comando para que System.CommandLine saiba quais parâmetros pertencem a cada local.
Crie os quatro subcomandos. Cada comando obtém sua própria combinação de opções e argumentos:
var addCommand = new Command("add", "Add a new task") { Arguments = { descriptionArgument }, Options = { priorityOption, dueOption } };var listCommand = new Command("list", "List all tasks") { Options = { allOption } };var completeCommand = new Command("complete", "Mark a task as complete") { Arguments = { taskIdArgument } };var removeCommand = new Command("remove", "Remove a task") { Arguments = { taskIdArgument } };Monte o comando root. O System.CommandLine.RootCommand é o ponto de entrada para a CLI. Adicione a opção global
--verbosee todos os subcomandos:var rootCommand = new RootCommand("A simple task tracker CLI") { Options = { verboseOption }, Subcommands = { addCommand, listCommand, completeCommand, removeCommand } };O texto de ajuda gerado automaticamente mostra a descrição do comando raiz quando o usuário executa
TaskCli --help.
Manipular comandos com ações
Cada subcomando precisa de uma ação. Uma ação é um delegado que é executado quando o usuário invoca esse comando. Um delegado é um tipo que representa uma referência a um método. Aqui, você passa uma expressão lambda (uma função anônima embutida definida com =>) como delegado. Chame SetAction para atribuir cada ação. O delegado recebe um ParseResult que fornece acesso a valores analisados por meio de GetValue.
Defina a ação para o
addcomando. Essa ação introduz interpolação de strings ($"..."strings que incorporam expressões entre chaves), o operador condicional (?:) e um padrão de teste de tipo (due is DateOnly dueDate) que verifica se um valor anulável possui um valor e o atribui a uma nova variável em uma única etapa:addCommand.SetAction(parseResult => { var description = parseResult.GetValue(descriptionArgument)!; var priority = parseResult.GetValue(priorityOption); var due = parseResult.GetValue(dueOption); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var id = tasks.Count > 0 ? tasks.Max(t => t.Id) + 1 : 1; var task = new TaskItem(id, description, priority, due, false); tasks.Add(task); SaveTasks(tasks); Console.WriteLine($"Added task {id}: {description}"); if (verbose) { Console.WriteLine($" Priority: {priority}"); if (due is DateOnly dueDate) { Console.WriteLine($" Due: {dueDate}"); } } });Defina a ação para o
listcomando. LINQ (Consulta Integrada à Linguagem) fornece operadores de consulta padrão para coleções na memória. Nesta ação, Enumerable.Where filtra as tarefas somente para os itens que correspondem a uma condição e Enumerable.ToList materializa a sequência filtrada em uma lista. Em seguida, a ação usa umforeachloop para iterar sobre os resultados e o operador condicional para definir um símbolo de status:listCommand.SetAction(parseResult => { var showAll = parseResult.GetValue(allOption); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var filtered = showAll ? tasks : tasks.Where(t => !t.IsComplete).ToList(); if (filtered.Count == 0) { Console.WriteLine("No tasks found."); return; } foreach (var task in filtered) { var status = task.IsComplete ? "✓" : " "; Console.WriteLine($" [{status}] {task.Id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); if (task.Due is DateOnly dueDate) { Console.WriteLine($" Due: {dueDate}"); } } } });Para obter mais detalhes, consulte LINQ.
Defina a ação para o
completecomando. Esta ação usa LINQ's Enumerable.FirstOrDefault para localizar:- Uma tarefa correspondente.
- Um
is nullpadrão para verificar se a tarefa existe. - Uma
withexpressão para criar uma nova instância de registro copiando os valores existentes primeiro e, em seguida, aplicando as propriedades definidas nowithinicializador (aqui,IsComplete = true). Os registros são imutáveis por padrão, portanto, esse padrão de cópia e atualização é como você produz um valor modificado.
Como a ação pode falhar (por exemplo, a ID da tarefa não existe), a ação retorna um código de erro inteiro que se torna o código de saída do aplicativo:
completeCommand.SetAction(parseResult => { var id = parseResult.GetValue(taskIdArgument); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var task = tasks.FirstOrDefault(t => t.Id == id); if (task is null) { Console.Error.WriteLine($"Task {id} not found."); return -1; } tasks[tasks.IndexOf(task)] = task with { IsComplete = true }; SaveTasks(tasks); Console.WriteLine($"Completed task {id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); } return 0; });Defina a ação para o
removecomando. Essa ação segue o mesmo padrão de pesquisa e validação quecomplete. UseFirstOrDefaultpara localizar a tarefa eis nulllidar com o caso ausente:removeCommand.SetAction(parseResult => { var id = parseResult.GetValue(taskIdArgument); var verbose = parseResult.GetValue(verboseOption); var tasks = LoadTasks(); var task = tasks.FirstOrDefault(t => t.Id == id); if (task is null) { Console.Error.WriteLine($"Task {id} not found."); return -1; } tasks.Remove(task); SaveTasks(tasks); Console.WriteLine($"Removed task {id}: {task.Description}"); if (verbose) { Console.WriteLine($" Priority: {task.Priority}"); } return 0; });Analise a linha de comando e invoque a ação correspondente:
return rootCommand.Parse(args).Invoke();rootCommand.Parse(args)analisa a entrada em um ParseResulte.Invoke()executa a ação para o comando correspondente. O valor retornado é um código de saída (0 para êxito).
Adicionar tipos de suporte e auxiliares de dados
O aplicativo precisa de algumas partes de suporte: funções locais, um enum, um recorde um contexto de serialização. As seções a seguir introduzem cada conceito, explicam por que você o escolheria e mostram o código. O aplicativo usa System.Text.Json para armazenar tarefas como JSON (JavaScript Object Notation). Aplicativos baseados em arquivo exigem que declarações de tipo apareçam após todas as instruções de nível superior e funções locais.
Adicione as funções locais que carregam e salvam tarefas. Uma função local é um método declarado dentro de outra função, incluindo dentro de outras funções locais. As funções locais mantêm a lógica auxiliar próxima ao código que a chama, o que melhora a legibilidade porque um leitor não precisa ir para uma classe ou arquivo separado para entender o fluxo. Aqui,
LoadTaskseSaveTasksencapsulam a entrada/saída de arquivo que várias ações de comando compartilham, para que o aplicativo grave a lógica de carregamento/salvamento uma vez e a reutilize:static string GetTaskFilePath() => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "taskcli-sample", "tasks.json"); static List<TaskItem> LoadTasks() { var path = GetTaskFilePath(); if (!File.Exists(path)) { return []; } var json = File.ReadAllText(path); return JsonSerializer.Deserialize(json, TaskJsonContext.Default.ListTaskItem) ?? []; } static void SaveTasks(List<TaskItem> tasks) { var path = GetTaskFilePath(); Directory.CreateDirectory(Path.GetDirectoryName(path)!); var json = JsonSerializer.Serialize(tasks, TaskJsonContext.Default.ListTaskItem); File.WriteAllText(path, json); }Adicione o
Priorityenum e oTaskItemregistro no final do arquivo.public enum Priority { Low, Medium, High }Um
enumé um tipo de valor que define um conjunto fixo de constantes nomeadas apoiadas por um tipo integral. Você pode representar níveis de prioridade com inteiros simples (0, 1, 2), mas umaenumopção é melhor por vários motivos: o compilador restringe as atribuições aos nomes definidos, portanto, um erro de digitação comoHihgcausa um erro de tempo de compilação em vez de um bug silencioso; os nomesLowMediumeHightornam o código mais legível; eSystem.CommandLinevalida automaticamente a entrada do usuário nos membros de enumeração, para que você obtenha verificação de entrada gratuita.public record TaskItem(int Id, string Description, Priority Priority, DateOnly? Due, bool IsComplete);Um
recordé um tipo que o compilador equipa com igualdade baseada em valor e mutação não destrutiva. Umrecordé adequado paraTaskItemporque os dados da tarefa são um estado simples sem comportamento complexo; você compara tarefas por seus valores, não pela identidade de referência. O compilador geraEquals,GetHashCode,ToStringewithcom suporte para expressões a partir dos parâmetros do construtor, para que você obtenha verificações de igualdade corretas, saída de depuração fácil e atualizações imutáveis sem escrever código repetitivo. Como os registros são imutáveis por padrão, você usa umawithexpressão para produzir uma cópia modificada (como a açãocompletefaz) em vez de alterar o original, o que impede efeitos colaterais acidentais quando ações diferentes leem e gravam a mesma lista.Adicione o contexto de serialização JSON para serialização compatível com AOT:
[System.Text.Json.Serialization.JsonSourceGenerationOptions(WriteIndented = true)] [System.Text.Json.Serialization.JsonSerializable(typeof(List<TaskItem>))] internal partial class TaskJsonContext : System.Text.Json.Serialization.JsonSerializerContext;Essa classe usa dois atributos (anotações de metadados colocadas em colchetes acima de uma declaração). O atributo
[JsonSourceGenerationOptions(WriteIndented = true)]informa ao gerador de código-fonte a emitir JSON formatado para legibilidade. O[JsonSerializable(typeof(List<TaskItem>))]atributo informa ao gerador para qual tipo criar código de serialização. Juntos, esses atributos permitem a serialização gerada pela fonte, o que evita a reflexão em tempo de execução e dá suporte à compilação antecipada em tempo (AOT).
Testar o aplicativo
Execute o aplicativo com entradas diferentes para exercitar cada subcomando.
Exiba a ajuda gerada automaticamente:
dotnet run TaskCli.cs -- --helpDescription: A simple task tracker CLI Usage: TaskCli [command] [options] Options: --verbose Show detailed output -?, -h, --help Show help and usage information --version Show version information Commands: add <description> Add a new task list List all tasks complete <id> Mark a task as complete remove <id> Remove a taskAdicione tarefas com opções diferentes:
dotnet run TaskCli.cs -- add "Write documentation" --priority High --due 2026-04-01 dotnet run TaskCli.cs -- add "Review pull request" dotnet run TaskCli.cs -- add "Fix build errors"Added task 1: Write documentation Added task 2: Review pull request Added task 3: Fix build errorsListar tarefas e usar
--verbosepara mostrar detalhes extras:dotnet run TaskCli.cs -- --verbose list[ ] 1: Write documentation Priority: High Due: 4/1/2026 [ ] 2: Review pull request Priority: Medium [ ] 3: Fix build errors Priority: MediumConclua uma tarefa e verifique se a lista filtra as tarefas concluídas por padrão:
dotnet run TaskCli.cs -- complete 2 dotnet run TaskCli.cs -- listCompleted task 2: Review pull request [ ] 1: Write documentation [ ] 3: Fix build errorsUse
--allpara incluir tarefas concluídas:dotnet run TaskCli.cs -- list --all[ ] 1: Write documentation [✓] 2: Review pull request [ ] 3: Fix build errorsRemova uma tarefa:
dotnet run TaskCli.cs -- remove 3Removed task 3: Fix build errors
Limpar os recursos
O rastreador de tarefas armazena dados em um arquivo JSON na pasta de dados do aplicativo local. Exclua a taskcli-sample pasta para remover os dados de exemplo:
-
Windows: Excluir
%LOCALAPPDATA%\taskcli-sample. -
macOS: Excluir
~/Library/Application Support/taskcli-sample. -
Linux: Excluir
~/.local/share/taskcli-sample.