Руководство. Начало работы с System.CommandLine

Важно!

System.CommandLine В настоящее время доступна предварительная версия, и эта документация предназначена для бета-версии 4 версии 2.0. Некоторые сведения относятся к предварительной версии продукта, который может быть существенно изменен перед выпуском. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

В этом руководстве показано, как создать приложение командной строки .NET, использующее библиотекуSystem.CommandLine. Начните с создания простой корневой команды, которая имеет один вариант. Затем вы добавите в эту базу, создав более сложное приложение, содержащее несколько подкоманд и различные параметры для каждой команды.

В этом руководстве описано следующее:

  • Создание команд, параметров и аргументов.
  • Укажите значения по умолчанию для параметров.
  • Назначение параметров и аргументов командам.
  • Рекурсивное назначение параметра всем подкомандам в команде.
  • Работа с несколькими уровнями вложенных подкомандов.
  • Создание псевдонимов для команд и параметров.
  • Работа с типами stringпараметров перечисления , string[]int, , boolFileInfo и .
  • Привяжите значения параметров к коду обработчика команд.
  • Используйте пользовательский код для анализа и проверки параметров.

Предварительные требования

либо

  • Visual Studio 2022 с установленной рабочей нагрузкой разработка классических приложений .NET .

Создание приложения

Создайте проект консольного приложения .NET 6 с именем "scl".

  1. Создайте папку с именем scl для проекта, а затем откройте командную строку в новой папке.

  2. Выполните следующую команду:

    dotnet new console --framework net6.0
    

Установите пакет System.CommandLine.

  • Выполните следующую команду:

    dotnet add package System.CommandLine --prerelease
    

    Параметр --prerelease необходим, так как библиотека все еще находится в бета-версии.

  1. Замените содержимое Program.cs кодом из этого примера.

    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));
        }
    }
    

Предыдущий код:

  • Создает параметр с именем --file типа FileInfo и назначает его корневой команде:

    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);
    
  • Указывает, что ReadFile является методом, который будет вызываться при вызове корневой команды:

    rootCommand.SetHandler((file) => 
        { 
            ReadFile(file!); 
        },
        fileOption);
    
  • Отображает содержимое указанного файла при вызове корневой команды:

    static void ReadFile(FileInfo file)
    {
        File.ReadLines(file.FullName).ToList()
            .ForEach(line => Console.WriteLine(line));
    }
    

Тестирование приложения

При разработке приложения командной строки можно использовать любой из следующих способов тестирования:

  • dotnet build Выполните команду , а затем откройте командную строку в папке scl/bin/Debug/net6.0, чтобы запустить исполняемый файл:

    dotnet build
    cd bin/Debug/net6.0
    scl --file scl.runtimeconfig.json
    
  • Используйте dotnet run и передайте значения параметров в приложение, а не run в команду , включив их после --, как показано в следующем примере:

    dotnet run -- --file scl.runtimeconfig.json
    

    В предварительной версии пакета SDK для .NET 7.0.100 можно использовать commandLineArgs файл файла launchSettings.json , выполнив команду dotnet run --launch-profile <profilename>.

  • Опубликуйте проект в папке, откройте командную строку в этой папке и запустите исполняемый файл:

    dotnet publish -o publish
    cd ./publish
    scl --file scl.runtimeconfig.json
    
  • В Visual Studio 2022 выберитев меню Свойства отладки> и введите параметры и аргументы в поле Аргументы командной строки. Пример:

    Аргументы командной строки в Visual Studio 2022

    Затем запустите приложение, например нажав клавиши CTRL+F5.

В этом руководстве предполагается, что вы используете первый из этих вариантов.

При запуске приложения отображается содержимое файла, указанного параметром --file .

{
  "runtimeOptions": {
    "tfm": "net6.0",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "6.0.0"
    }
  }
}

Выходные данные справки

System.CommandLine автоматически предоставляет выходные данные справки:

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

Выходные данные версии

System.CommandLine автоматически предоставляет выходные данные версии:

scl --version
1.0.0

Добавление подкоманды и параметров

В этом разделе выполняются следующие действия:

  • Создайте дополнительные параметры.
  • Создайте подкоманду.
  • Назначьте новые параметры новой подкоманде.

Новые параметры позволяют настроить цвета текста переднего плана и фона, а также скорость считывания. Эти функции будут использоваться для чтения коллекции кавычек из руководства по консольным приложениям Teleprompter.

  1. Скопируйте в каталог проекта файл sampleQuotes.txt из репозитория GitHub для этого примера. Сведения о скачивании файлов см. в разделе Примеры и учебники.

  2. Откройте файл проекта и добавьте <ItemGroup> элемент непосредственно перед закрывающим </Project> тегом:

    <ItemGroup>
      <Content Include="sampleQuotes.txt">
        <CopyToOutputDirectory>Always</CopyToOutputDirectory>
      </Content>
    </ItemGroup>
    

    При добавлении этой разметки текстовый файл копируется в папку bin/debug/net6.0 при сборке приложения. Таким образом, при запуске исполняемого файла в этой папке вы можете получить доступ к файлу по имени, не указывая путь к папке.

  3. В файле Program.cs после кода, который создает --file параметр, создайте параметры для управления скоростью считывания и цветами текста:

    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. После строки, создающей корневую команду, удалите строку, которая добавляет в нее --file параметр . Вы удаляете его здесь, так как вы добавите его в новую подкоманду.

    var rootCommand = new RootCommand("Sample app for System.CommandLine");
    //rootCommand.AddOption(fileOption);
    
  5. После строки, создающей корневую read команду, создайте подкоманду. Добавьте параметры в эту подкоманду, а подкоманду — в команду root.

    var readCommand = new Command("read", "Read and display the file.")
        {
            fileOption,
            delayOption,
            fgcolorOption,
            lightModeOption
        };
    rootCommand.AddCommand(readCommand);
    
  6. Замените SetHandler код следующим SetHandler кодом для новой подкоманды:

    readCommand.SetHandler(async (file, delay, fgcolor, lightMode) =>
        {
            await ReadFile(file!, delay, fgcolor, lightMode);
        },
        fileOption, delayOption, fgcolorOption, lightModeOption);
    

    Вы больше не вызываете SetHandler команду root, так как ей больше не нужен обработчик. Если команда содержит подкоманды, обычно при вызове приложения командной строки необходимо указать одну из подкоманд.

  7. Замените метод обработчика ReadFile следующим кодом:

    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);
        };
    }
    

Теперь приложение выглядит следующим образом:

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);
        };
    }
}

Тестирование новой подкоманды

Теперь при попытке запустить приложение без указания подкоманды вы получите сообщение об ошибке, за которым следует справочное сообщение, указывающее доступную подкоманду.

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.

Текст справки для подкоманды read показывает, что доступны четыре варианта. В нем отображаются допустимые значения для перечисления.

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

Выполните подкоманду read , указав только --file параметр , и вы получите значения по умолчанию для трех других параметров.

scl read --file sampleQuotes.txt

Задержка в 42 миллисекундах на символ по умолчанию приводит к снижению скорости чтения. Вы можете ускорить его, задав --delay меньшее число.

scl read --file sampleQuotes.txt --delay 0

Для задания цветов текста можно использовать --fgcolor и --light-mode :

scl read --file sampleQuotes.txt --fgcolor red --light-mode

Укажите недопустимое значение для --delay , и вы получите сообщение об ошибке:

scl read --file sampleQuotes.txt --delay forty-two
Cannot parse argument 'forty-two' for option '--int' as expected type 'System.Int32'.

Укажите недопустимое значение для --file , и вы получите исключение:

scl read --file nofile
Unhandled exception: System.IO.FileNotFoundException:
Could not find file 'C:\bin\Debug\net6.0\nofile'.

Добавление подкоманд и настраиваемая проверка

В этом разделе создается окончательная версия приложения. По завершении приложение будет иметь следующие команды и параметры:

  • Команда root с параметром global* с именем --file
    • Команда quotes
      • read команда с параметрами с именами --delay, --fgcolorи --light-mode
      • add команда с аргументами с именами quote и byline
      • delete команда с параметром с именем --search-terms

* Глобальный параметр доступен для команды, которой он назначен, и рекурсивно для всех ее подкоманд.

Ниже приведен пример входных данных командной строки, который вызывает каждую из доступных команд со своими параметрами и аргументами:

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. В файле Program.cs замените код, создающий --file параметр, следующим кодом:

    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);
            }
        });
    

    Этот код использует для ParseArgument<T> предоставления пользовательского анализа, проверки и обработки ошибок.

    Без этого кода отсутствующие файлы передаются с исключением и трассировкой стека. В этом коде отображается только указанное сообщение об ошибке.

    Этот код также задает значение по умолчанию, поэтому он задает значение isDefaulttrue. Если не задано значение isDefaulttrue, parseArgument делегат не будет вызываться, если для --fileне предоставлены входные данные.

  2. После кода, создающего lightModeOption, добавьте параметры и аргументы для add команд и 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.");
    

    Параметр AllowMultipleArgumentsPerToken позволяет опустить --search-terms имя параметра при указании элементов в списке после первого. Ниже приведены примеры эквивалентных входных данных из командной строки:

    scl quotes delete --search-terms David "You can do"
    scl quotes delete --search-terms David --search-terms "You can do"
    
  3. Замените код, который создает корневую read команду и команду, следующим кодом:

    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);
    

    Этот код вносит следующие изменения:

    • Удаляет --file параметр из read команды .

    • Добавляет параметр в --file качестве глобального параметра в корневую команду.

    • Создает quotes команду и добавляет ее в корневую команду.

    • read Добавляет команду в команду , quotes а не в корневую команду.

    • Создает add команды и delete и добавляет их в quotes команду .

    Результатом будет следующая иерархия команд:

    • Корневая команда
      • quotes
        • read
        • add
        • delete

    Теперь приложение реализует рекомендуемый шаблон, в котором родительская команда (quotes) указывает область или группу, а ее дочерние команды (read, add, delete) являются действиями.

    Глобальные параметры применяются к команде и рекурсивно к подкомандам. Так как --file находится в корневой команде, она будет автоматически доступна во всех подкомандах приложения.

  4. После кода SetHandler добавьте новый SetHandler код для новых подкоманд:

    deleteCommand.SetHandler((file, searchTerms) =>
        {
            DeleteFromFile(file!, searchTerms);
        },
        fileOption, searchTermsOption);
    
    addCommand.SetHandler((file, quote, byline) =>
        {
            AddToFile(file!, quote, byline);
        },
        fileOption, quoteArgument, bylineArgument);
    

    У подкоманды quotes нет обработчика, так как она не является конечной командой. Подкоманды read, addи delete являются конечными командами в quotes, и SetHandler вызывается для каждой из них.

  5. Добавьте обработчики для add и 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();
    }
    

Готовое приложение выглядит следующим образом:

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();
    }
}

Выполните сборку проекта, а затем выполните следующие команды.

Отправьте несуществующий файл в с --fileread помощью команды , и вы получите сообщение об ошибке вместо исключения и трассировки стека:

scl quotes read --file nofile
File does not exist

Попробуйте выполнить подкоманду quotes , и вы получите сообщение с указанием использовать read, addили 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.

Выполните подкоманду add, а затем просмотрите конец текстового файла, чтобы увидеть добавленный текст:

scl quotes add "Hello world!" "Nancy Davolio"

Выполните подкоманду delete со строками поиска из начала файла, а затем просмотрите начало текстового файла, чтобы увидеть, где текст был удален:

scl quotes delete --search-terms David "You can do" Antoine "Perfection is achieved"

Примечание

Если вы работаете в папке bin/debug/net6.0 , в этой папке находится файл с изменениями из add команд и delete . Копия файла в папке проекта остается неизменной.

Дальнейшие действия

В этом руководстве вы создали простое приложение командной строки, использующее System.CommandLine. Дополнительные сведения о библиотеке см. в обзореSystem.CommandLine.