Compartir a través de


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 stringlos tipos de opción, string[], intbool, 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

Or

Creación de la aplicación

Crea un proyecto de aplicación de consola de .NET 6 denominado «scl».

  1. Crea una carpeta denominada scl para el proyecto y abre un símbolo del sistema en la nueva carpeta.

  2. 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.

  1. 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 al run 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 comando dotnet 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:

    Argumentos de línea de comandos en Visual Studio 2022

    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.

  1. 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.

  2. 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.

  3. 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.");
    
  4. 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);
    
  5. 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);
    
  6. Reemplaza el SetHandlercódigo generado con el código siguiente SetHandlerpara 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.

  7. 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, --fgcolory --light-mode
      • add comando con argumentos denominados quote y byline
      • delete comando con la opción denominada --search-terms

* 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"
  1. 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 en true. Si no establece isDefaulten true, no se llama al parseArgument delegado cuando no se proporciona ninguna entrada para --file.

  2. Después del código que crea lightModeOption, agrega opciones y argumentos para los comandos add y delete:

    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"
    
  3. 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 de read 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 al quotes comando en lugar de al comando raíz.

    • Crea comandos add y delete los agrega al comando quotes.

    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.

  4. Después del SetHandler código, agrega código nuevo SetHandler 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 subcomandos read, addy delete son comandos hoja en quotesy SetHandler se llama a para cada uno de ellos.

  5. Agrega los controladores para add y delete.

    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, addo 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 addy, 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.