Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Sugerencia
Este artículo forma parte de la sección Aspectos básicos , escrito para desarrolladores que conocen al menos un lenguaje de programación y están aprendiendo C#. Si no está familiarizado con la programación, comience con Introducción. Si necesita cobertura completa de la biblioteca, consulte la documentación de la biblioteca System.CommandLine.
La System.CommandLine biblioteca controla el análisis de la línea de comandos, la generación de texto de ayuda y la validación de entrada para que pueda centrarse en la lógica de la aplicación. En este tutorial, creará una CLI de seguimiento de tareas que muestra los conceptos básicos: comandos, subcomandos, opciones y argumentos.
En este tutorial, usted hará lo siguiente:
- Cree una aplicación basada en archivos mediante el
System.CommandLinepaquete. - Defina opciones y argumentos con valores tipados.
- Cree subcomandos y adjunte opciones y argumentos a ellos.
- Controle cada subcomando con una acción.
- Pruebe la aplicación con entradas de línea de comandos diferentes.
Prerrequisitos
- Instale el SDK de .NET 10 o posterior.
Creación de la aplicación
Empiece por crear un programa de C# basado en archivos y agregar el System.CommandLine paquete.
Cree un archivo denominado
TaskCli.cscon el siguiente contenido:#!/usr/bin/env dotnetLa
#!línea (shebang) le permite ejecutar el archivo directamente en sistemas Unix. En Windows, ejecute el archivo condotnet run TaskCli.cs.Agregue la directiva del paquete
System.CommandLiney las declaraciones necesariasusing:#:package System.CommandLine@2.0.0using System.CommandLine; using System.CommandLine.Parsing; using System.Text.Json;Importante
La versión
2.0.0es la versión más reciente en el momento de escribir. Compruebe la página NuGet del paquete para obtener la versión más reciente para asegurarse de que tiene las correcciones de seguridad más recientes.
Descripción de la estructura de comandos
Antes de escribir cualquier código de análisis, tenga en cuenta el aspecto de la CLI desde la perspectiva del usuario. El rastreador de tareas admite cuatro operaciones:
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
Nota:
Después de TaskCli.cs en los ejemplos anteriores, dotnet run indica que todos los argumentos restantes se pasan a su aplicación en lugar de ser interpretados por la propia dotnet CLI.
Cada línea usa varios conceptos de línea de comandos:
-
Los subcomandos son verbos que indican a la aplicación qué hacer. El rastreador de tareas tiene cuatro:
add,list,completeyremove. Cada subcomando puede definir sus propios parámetros. -
Los argumentos son valores posicionales que siguen un subcomando. En
add "Write documentation", la cadena"Write documentation"es un argumento que especifica la descripción de la tarea. Encomplete 3, el número3es un argumento que especifica el identificador de tarea. -
Las opciones son valores con nombre que tienen el prefijo
--. Enadd --priority High --due 2026-04-01, y--priority--dueson opciones con sus propios valores. Enlist --all, la--allopción es una marca booleana que no necesita un valor. -
Las opciones globales se aplican a cada subcomando. La
--verboseopción se define en el comando raíz conRecursive = true, por lo que funciona con cualquier subcomando. En--verbose list, la marca detallada aparece antes del subcomando, perolist --verbosefunciona igualmente bien.
En las secciones siguientes, creará estas piezas de abajo hacia arriba. En primer lugar, defina las opciones individuales (como --priority y --all) y los argumentos (como la descripción de la tarea y el identificador). A continuación, cree los cuatro subcomandos y adjunte las opciones y argumentos pertinentes a cada uno. A continuación, asigne una acción para cada subcomando. La acción es el código que se ejecuta cuando el usuario invoca ese comando. Por último, ensambla el comando raíz, analiza la entrada e invoca la acción coincidente.
Para obtener un vistazo más profundo a los conceptos de sintaxis de la línea de comandos, consulte Introducción a la sintaxis de la línea de comandos.
Definir opciones y argumentos
Las opciones representan valores con nombre que los usuarios especifican con un -- prefijo. Los argumentos representan valores posicionales. Ambos están fuertemente tipados.
System.CommandLine analiza la cadena de entrada al tipo que especifiques.
La System.CommandLine biblioteca usa tipos genéricos para aplicar la seguridad de tipos. Al escribir Option<int>, el int entre corchetes angulares es un argumento de tipo. Indica a la biblioteca qué tipo de valor contiene la opción. La propia clase declara un parámetroT de tipo (como en System.CommandLine.Option<T>), y se proporciona el tipo concreto al crear una instancia. La biblioteca analiza la cadena de entrada del usuario y la convierte automáticamente en ese tipo. Si el usuario proporciona --delay abc para Option<int>, System.CommandLine notifica un error de análisis sintáctico en vez de pasar datos incorrectos a su código. Verá este patrón con Option<bool>, Option<Priority>, Option<DateOnly?>y System.CommandLine.Argument<T> en los pasos siguientes. Para más información sobre genéricos, vea Generics.
Defina las opciones. Cada
Option<T>especifica el tipo de valor, el nombre y una descripción. La--priorityopción usa unenumtipo ySystem.CommandLinevalida automáticamente la entrada con los valores de enumeración válidos: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" };La
Recursive = trueconfiguración de--verbosehace que la opción esté disponible en cada subcomando. ElDefaultValueFactoryen--priorityproporciona un valor predeterminado para que los usuarios puedan omitir la opción.Defina los argumentos. Cada
Argument<T>especifica el tipo de valor y un nombre:var descriptionArgument = new Argument<string>("description") { Description = "Task description" }; var taskIdArgument = new Argument<int>("id") { Description = "Task ID" };
Comandos y subcomandos de compilación
System.CommandLine.Command representa una acción que el usuario puede invocar. Agregue las opciones y argumentos pertinentes a cada comando para System.CommandLine saber qué parámetros pertenecen a dónde.
Cree los cuatro subcomandos. Cada comando obtiene su propia combinación de opciones y 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 } };Ensamblar el comando raíz. System.CommandLine.RootCommand es el punto de entrada de la CLI. Agregue la opción global
--verbosey todos los subcomandos:var rootCommand = new RootCommand("A simple task tracker CLI") { Options = { verboseOption }, Subcommands = { addCommand, listCommand, completeCommand, removeCommand } };El texto de ayuda generado automáticamente muestra la descripción del comando raíz cuando el usuario ejecuta
TaskCli --help.
Manejo de comandos con acciones
Cada subcomando necesita una acción. Una acción es un delegado que se ejecuta cuando el usuario invoca ese comando. Un delegado es un tipo que representa una referencia a un método . Aquí, se pasa una expresión lambda (una función anónima insertada definida con =>) como delegado. Llame SetAction para asignar cada acción. El delegado recibe un ParseResult que proporciona acceso a los valores procesados a través de GetValue.
Establezca la acción para el
addcomando. Esta acción presenta la interpolación de cadenas ($"..."cadenas que insertan expresiones entre llaves), el operador condicional (?:) y un patrón de prueba de tipo (due is DateOnly dueDate) que comprueba si un valor anulable tiene un valor y lo asigna a una nueva variable de un solo paso: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}"); } } });Establezca la acción para el
listcomando. LINQ (Language Integrated Query) proporciona operadores de consulta estándar para colecciones en memoria. En esta acción, Enumerable.Where filtra las tareas solo a los elementos que coinciden con una condición y Enumerable.ToList materializa la secuencia filtrada en una lista. A continuación, la acción usa unforeachbucle para recorrer en iteración los resultados y el operador condicional para elegir un símbolo de estado: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 obtener más información, consulte LINQ.
Establezca la acción para el
completecomando. Esta acción usa LINQ Enumerable.FirstOrDefault para buscar:- Una tarea coincidente.
- Patrón
is nullpara comprobar si la tarea existe. -
Expresión
withpara crear una nueva instancia de registro copiando primero los valores existentes y aplicando las propiedades establecidas en elwithinicializador (aquí,IsComplete = true). Los registros son inmutables de forma predeterminada, por lo que este patrón de copia y actualización es cómo se genera un valor modificado.
Dado que la acción puede producir un error (por ejemplo, el identificador de tarea no existe), la acción devuelve un código de error entero que se convierte en el código de salida de la aplicación:
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; });Establezca la acción para el
removecomando. Esta acción sigue el mismo patrón de búsqueda y validación quecomplete. UseFirstOrDefaultpara buscar la tarea yis nullpara manejar el caso faltante.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; });Analice la línea de comandos e invoque la acción coincidente:
return rootCommand.Parse(args).Invoke();rootCommand.Parse(args)analiza la entrada en ParseResulty.Invoke()ejecuta la acción para el comando coincidente. El valor devuelto es un código de salida (0 para éxito).
Agregar tipos auxiliares y herramientas de datos
La aplicación necesita algunas partes auxiliares: funciones locales, , enuma recordy un contexto de serialización. En las secciones siguientes se presenta cada concepto, se explica por qué se elegiría y se muestra el código. La aplicación usa System.Text.Json para almacenar tareas como JSON (notación de objetos JavaScript). Las aplicaciones basadas en archivos requieren que aparezcan declaraciones de tipo después de todas las instrucciones de nivel superior y las funciones locales.
Agregue las funciones locales que cargan y guardan tareas. Una función local es un método declarado dentro de otra función, incluida dentro de otras funciones locales. Las funciones locales mantienen la lógica auxiliar cerca del código que lo llama, lo que mejora la legibilidad porque un lector no tiene que saltar a una clase o archivo independiente para comprender el flujo. Aquí,
LoadTasksySaveTasksencapsulan la E/S de archivos compartida por múltiples acciones de comando, de modo que la aplicación escribe la lógica de carga/guardado una sola vez y la reutiliza.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); }Agregue la
Priorityenumeración yTaskItemel registro al final del archivo:public enum Priority { Low, Medium, High }Un
enumes un tipo de valor que define un conjunto fijo de constantes con nombre respaldadas por un tipo integral. Puede representar niveles de prioridad con enteros simples (0, 1, 2), pero unaenumes una mejor elección por varias razones: el compilador restringe las asignaciones a los nombres definidos, de modo que un error tipográfico comoHihgprovoca un error en tiempo de compilación en lugar de un error silencioso; los nombresLow,Medium, yHighhacen que el código sea más legible; ySystem.CommandLinevalida automáticamente la entrada del usuario en comparación con los miembros de la enumeración, por lo que obtiene una validación automática de entrada.public record TaskItem(int Id, string Description, Priority Priority, DateOnly? Due, bool IsComplete);Un
recordes un tipo que el compilador equipa con igualdad basada en valores y mutación no destructiva. Unrecordes la opción adecuada paraTaskItemporque los datos de tareas son de estado simple sin ningún comportamiento complejo; las tareas se comparan por sus valores y no por su identidad de referencia. El compilador genera compatibilidad conEquals,GetHashCode,ToStringywitha partir de los parámetros del constructor, por lo que se obtienen comprobaciones de igualdad correctas, salida de depuración sencilla y actualizaciones inmutables sin escribir código repetitivo. Dado que los registros son inmutables de forma predeterminada, se usa unawithexpresión para generar una copia modificada (como hace lacompleteacción) en lugar de mutar el original, lo que evita efectos secundarios accidentales cuando diferentes acciones leen y escriben la misma lista.Agregue el contexto de serialización JSON para la serialización compatible con 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;Esta clase usa dos atributos (anotaciones de metadatos colocadas entre corchetes encima de una declaración). El atributo
[JsonSourceGenerationOptions(WriteIndented = true)]indica al generador de origen que emita JSON con sangría para mejorar la legibilidad. El[JsonSerializable(typeof(List<TaskItem>))]atributo indica al generador para qué tipo se va a crear código de serialización. Juntos, estos atributos habilitan la serialización generada por código fuente, lo que evita la reflexión en tiempo de ejecución y admite la compilación AOT (Ahead Of Time).
Prueba de la aplicación
Ejecute la aplicación con diferentes entradas para ejercer cada subcomando.
Vea la ayuda generada automáticamente:
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 taskAgregue tareas con diferentes opciones:
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 errorsEnumerar tareas y usar
--verbosepara mostrar detalles adicionales: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: MediumComplete una tarea y compruebe que los filtros de lista han completado las tareas de forma predeterminada:
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 tareas completadas:dotnet run TaskCli.cs -- list --all[ ] 1: Write documentation [✓] 2: Review pull request [ ] 3: Fix build errorsQuitar una tarea:
dotnet run TaskCli.cs -- remove 3Removed task 3: Fix build errors
Limpieza de recursos
El rastreador de tareas almacena datos en un archivo JSON en la carpeta de datos de la aplicación local. Elimine la taskcli-sample carpeta para quitar los datos de ejemplo:
-
Windows: elimine
%LOCALAPPDATA%\taskcli-sample. -
macOS: elimine
~/Library/Application Support/taskcli-sample. -
Linux: elimine
~/.local/share/taskcli-sample.