Tutorial: Introducción a System.CommandLine
Importante
System.CommandLine
se encuentra actualmente en versión preliminar y esta documentación es para la versión 2.0 beta 4.
Parte de la información hace referencia a la versión preliminar del producto, que puede haberse modificado sustancialmente antes de lanzar la versión definitiva. Microsoft no otorga ninguna garantía, explícita o implícita, con respecto a la información proporcionada aquí.
En este tutorial se muestra cómo crear una aplicación de línea de comandos de .NET que usa la System.CommandLine
biblioteca. Empezaráa creando un comando raíz simple que tenga una opción. A continuación, agregarás a esa base, creando una aplicación más compleja que contenga varios subcomandos y diferentes opciones para cada comando.
En este tutorial, aprenderá a:
- Crear comandos, opciones y argumentos ocultos.
- Especifica los valores predeterminados de las opciones.
- Asigna opciones y argumentos a comandos.
- Asigna una opción de forma recursiva a todos los subcomandos en un comando.
- Trabaja con varios niveles de subcomandos anidados.
- Crea alias para comandos y opciones.
- Trabaja con
string
los tipos de opción,string[]
,int
bool
,FileInfo
y enum. - Enlaza los valores de opción al código del controlador de comandos.
- Usa código personalizado para analizar y validar opciones.
Requisitos previos
- Editor de código, como Visual Studio Code con la extensión de C#.
- El SDK de .NET 6.
Or
- Visual Studio 2022 con la carga de trabajo de desarrollo de escritorio de .NET instalada.
Creación de la aplicación
Crea un proyecto de aplicación de consola de .NET 6 denominado «scl».
Crea una carpeta denominada scl para el proyecto y abre un símbolo del sistema en la nueva carpeta.
Ejecute el siguiente comando:
dotnet new console --framework net6.0
Instalar el paquete System.CommandLine
Ejecute el siguiente comando:
dotnet add package System.CommandLine --prerelease
La
--prerelease
opción es necesaria porque la biblioteca todavía está en versión beta.
Reemplace el contenido de Program.cs por el código siguiente:
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)); } }
El código anterior:
Crea una opción denominada
--file
de tipo FileInfo y la asigna al comando raíz: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
es el método al que se llamará cuando se invoque el comando raíz:rootCommand.SetHandler((file) => { ReadFile(file!); }, fileOption);
Muestra el contenido del archivo especificado cuando se invoca el comando raíz:
static void ReadFile(FileInfo file) { File.ReadLines(file.FullName).ToList() .ForEach(line => Console.WriteLine(line)); }
Prueba de la aplicación
Puedes usar cualquiera de las siguientes maneras de probar al desarrollar una aplicación de línea de comandos:
Ejecuta el
dotnet build
comando y abra un símbolo del sistema en la carpeta scl/bin/Debug/net6.0 para ejecutar el ejecutable:dotnet build cd bin/Debug/net6.0 scl --file scl.runtimeconfig.json
Usa
dotnet run
y pasa valores de opción a la aplicación en lugar de alrun
comando incluyéndolas después de--
, como en el ejemplo siguiente:dotnet run -- --file scl.runtimeconfig.json
En la versión preliminar del SDK de .NET 7.0.100, puedes usar el
commandLineArgs
de un archivo launchSettings.json ejecutando el comandodotnet run --launch-profile <profilename>
.Publica el proyecto en una carpeta, abra un símbolo del sistema en esa carpeta y ejecuta el archivo ejecutable:
dotnet publish -o publish cd ./publish scl --file scl.runtimeconfig.json
En Visual Studio 2022, seleccionaDepurar Propiedades de >depuración en el menú y escribe las opciones y los argumentos en el cuadro Argumentos de la línea de comandos. Por ejemplo:
A continuación, ejecuta la aplicación, por ejemplo presionando Ctrl+F5.
En este tutorial se da por supuesto que usas la primera de estas opciones.
Al ejecutar la aplicación, muestra el contenido del archivo especificado por la --file
opción.
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
}
}
}
Salida de la Ayuda
System.CommandLine
proporciona automáticamente la salida de ayuda:
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
Salida de la versión
System.CommandLine
proporciona automáticamente la salida de la versión:
scl --version
1.0.0
Agregar un subcomando y opciones
En esta sección:
- Crea más opciones.
- Crea un subcomando.
- Asigna las nuevas opciones al nuevo subcomando.
Las nuevas opciones le permitirán configurar los colores de texto en primer plano y de fondo y la velocidad de lectura. Estas características se usarán para leer una colección de comillas que proceden del tutorial de la aplicación de consola Teleprompter.
Copie el archivo sampleQuotes.txt del repositorio de GitHub de este ejemplo en su directorio del proyecto. Para obtener información sobre cómo descargar archivos, consulta las instrucciones en Ejemplos y tutoriales.
Abre el archivo del proyecto y agrega un
<ItemGroup>
elemento justo antes de la etiqueta de cierre</Project>
:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>
Al agregar este marcado, el archivo de texto se copiará en la carpeta bin/debug/net6.0 al compilar la aplicación. Por lo tanto, al ejecutar el archivo ejecutable en esa carpeta, puedes acceder al archivo por nombre sin especificar una ruta de acceso de carpeta.
En Program.cs, después del código que crea la opción
--file
, crea opciones para controlar la velocidad de lectura y los colores de 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.");
Después de la línea que crea el comando raíz, elimina la línea que le agrega la
--file
opción. Lo vas a quitar aquí porque lo agregarás a un nuevo subcomando.var rootCommand = new RootCommand("Sample app for System.CommandLine"); //rootCommand.AddOption(fileOption);
Después de la línea que crea el comando raíz, crea un
read
subcomando. Agrega las opciones a este subcomando y agrega el subcomando al comando raíz.var readCommand = new Command("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.AddCommand(readCommand);
Reemplaza el
SetHandler
código generado con el código siguienteSetHandler
para el nuevo subcomando:readCommand.SetHandler(async (file, delay, fgcolor, lightMode) => { await ReadFile(file!, delay, fgcolor, lightMode); }, fileOption, delayOption, fgcolorOption, lightModeOption);
Ya no estás llamando
SetHandler
al comando raíz porque el comando raíz ya no necesita un controlador. Cuando un comando tiene subcomandos, normalmente tienes que especificar uno de los subcomandos al invocar una aplicación de línea de comandos.Reemplaza el método del controlador
ReadFile
por el siguiente 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); }; }
La aplicación tiene el aspecto siguiente:
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);
};
}
}
Prueba del nuevo subcomando
Ahora, si intentas ejecutar la aplicación sin especificar el subcomando, recibirás un mensaje de error seguido de un mensaje de ayuda que especifica el subcomando que está disponible.
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.
El texto de ayuda de subcomando read
muestra que hay cuatro opciones disponibles. Muestra valores válidos para la enumeración.
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
Ejecuta el subcomando read
especificando solo la --file
opción y obtendrás los valores predeterminados para las otras tres opciones.
scl read --file sampleQuotes.txt
El retraso predeterminado de 42 milisegundos por carácter provoca una velocidad de lectura lenta. Puedes acelerarlo estableciendo --delay
en un número menor.
scl read --file sampleQuotes.txt --delay 0
Puedes usar --fgcolor
y --light-mode
para establecer colores de texto:
scl read --file sampleQuotes.txt --fgcolor red --light-mode
Proporciona un valor no válido para --delay
y recibes un mensaje de error:
scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.
Proporcione un valor no válido para --file
y obtienes una excepción:
scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.
Adición de subcomandos y validación personalizada
En esta sección se crea la versión final de la aplicación. Cuando termine, la aplicación tendrá los siguientes comandos y opciones:
- comando raíz con una opción global* denominada
--file
- El comando
quotes
read
comando con opciones denominadas--delay
,--fgcolor
y--light-mode
add
comando con argumentos denominadosquote
ybyline
delete
comando con la opción denominada--search-terms
- El comando
* Hay disponible una opción global para el comando al que está asignado y de forma recursiva a todos sus subcomandos.
Esta es la entrada de línea de comandos de ejemplo que invoca cada uno de los comandos disponibles con sus opciones y 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"
En Program.cs, reemplaza el código que crea la
--file
opción por el código siguiente: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 usa ParseArgument<T> para proporcionar análisis, validación y control de errores personalizados.
Sin este código, los archivos que faltan se notifican con una excepción y un seguimiento de pila. Con este código solo se muestra el mensaje de error especificado.
Este código también especifica un valor predeterminado, por lo que establece
isDefault
entrue
. Si no estableceisDefault
entrue
, no se llama alparseArgument
delegado cuando no se proporciona ninguna entrada para--file
.Después del código que crea
lightModeOption
, agrega opciones y argumentos para los comandosadd
ydelete
: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.");
La AllowMultipleArgumentsPerToken configuración permite omitir el nombre de la
--search-terms
opción al especificar elementos en la lista después del primero. Hace que los ejemplos siguientes de entrada de línea de comandos sea equivalente:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"
Reemplaza el código que crea el comando raíz y el
read
comando por el código siguiente: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 realiza los cambios siguientes:
Elimina la opción
--file
de la línea deread
comandos.Agrega la
--file
opción como opción global al comando raíz.Crea un
quotes
comando y lo agrega al comando raíz.Agrega el
read
comando alquotes
comando en lugar de al comando raíz.Crea comandos
add
ydelete
los agrega al comandoquotes
.
El resultado es la siguiente jerarquía de comandos:
- Comando raíz
quotes
read
add
delete
La aplicación ahora implementa el patrón recomendado donde el comando primario (
quotes
) especifica un área o grupo, y sus comandos secundarios (read
,add
,delete
) son acciones.Las opciones globales se aplican al comando y de forma recursiva a los subcomandos. Puesto
--file
que está en el comando raíz, estará disponible automáticamente en todos los subcomandos de la aplicación.Después del
SetHandler
código, agrega código nuevoSetHandler
para los nuevos subcomandos:deleteCommand.SetHandler((file, searchTerms) => { DeleteFromFile(file!, searchTerms); }, fileOption, searchTermsOption); addCommand.SetHandler((file, quote, byline) => { AddToFile(file!, quote, byline); }, fileOption, quoteArgument, bylineArgument);
El subcomando
quotes
no tiene un controlador porque no es un comando hoja. Los subcomandosread
,add
ydelete
son comandos hoja enquotes
ySetHandler
se llama a para cada uno de ellos.Agrega los controladores para
add
ydelete
.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(); }
La aplicación finalizada tiene el siguiente aspecto:
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();
}
}
Compila el proyecto y prueba los siguientes comandos.
Envía un archivo inexistente a --file
con el read
comando y obtén un mensaje de error en lugar de una excepción y un seguimiento de pila:
scl quotes read --file nofile
File does not exist
Intenta ejecutar el subcomando quotes
y obtén un mensaje que le dirija a usar read
, add
o 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.
Ejecuta el subcomando add
y, a continuación, examina el final del archivo de texto para ver el texto agregado:
scl quotes add "Hello world!" "Nancy Davolio"
Ejecuta el subcomando delete
con cadenas de búsqueda desde el principio del archivo y, a continuación, examina el principio del archivo de texto para ver dónde se quitó el texto:
scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"
Nota
Si se ejecuta en la carpeta bin/debug/net6.0, esa carpeta es donde encontrarás el archivo con los cambios de los comandos add
y delete
. La copia del archivo en la carpeta del proyecto permanece sin cambios.
Pasos siguientes
En este tutorial, has creado una aplicación de línea de comandos sencilla que usa System.CommandLine
. Para obtener más información sobre la biblioteca, consulta System.CommandLine información general.