Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
В этом руководстве показано, как создать приложение командной строки .NET, использующее библиотеку System.CommandLine. Сначала вы создадите простую корневую команду, которая имеет один вариант. Затем вы создадите более сложное приложение, содержащее несколько вложенных команд и различные параметры для каждой команды.
В этом руководстве описано, как:
- Создание команд, параметров и аргументов.
- Укажите значения по умолчанию для параметров.
- Назначение параметров и аргументов командам.
- Назначьте опцию рекурсивно всем подкомандам под командой.
- Работа с несколькими уровнями вложенных подкоманд.
- Создайте псевдонимы для команд и параметров.
- Работа с типами
string,string[],int,bool,FileInfoи перечисления. - Чтение значений опций в коде выполнения команды.
- Используйте пользовательский код для анализа и проверки параметров.
Предпосылки
- Последняя версия .NET SDK
- Visual Studio Code редактор
- C# DevKit
Или
- Visual Studio 2022 с установленным компонентом разработки десктопных приложений .NET.
Создание приложения
Создайте проект консольного приложения .NET 9 с именем SCL.
Создайте папку с именем scl для проекта, а затем откройте командную строку в новой папке.
Выполните следующую команду:
dotnet new console --framework net9.0
Установка пакета System.CommandLine
Выполните следующую команду:
dotnet add package System.CommandLine --prereleaseИли в .NET 10+:
dotnet package add System.CommandLine --prereleaseПараметр
--prereleaseнеобходим, так как библиотека по-прежнему находится в бета-версии.
Анализ аргументов
Замените содержимое файла Program.cs кодом, приведенным ниже.
using System.CommandLine; using System.CommandLine.Parsing; namespace scl; class Program { static int Main(string[] args) { Option<FileInfo> fileOption = new("--file") { Description = "The file to read and display on the console." }; RootCommand rootCommand = new("Sample app for System.CommandLine"); rootCommand.Options.Add(fileOption); ParseResult parseResult = rootCommand.Parse(args); if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile) { ReadFile(parsedFile); return 0; } foreach (ParseError parseError in parseResult.Errors) { Console.Error.WriteLine(parseError.Message); } return 1; } static void ReadFile(FileInfo file) { foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); } } }
Предыдущий код:
- Создает параметр с именем
--fileтипа FileInfo и добавляет его в корневую команду:
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
rootCommand.Options.Add(fileOption);
- Анализирует
args, и проверяет, было ли указано какое-либо значение для параметра--file. В этом случае вызывается методReadFile, используя разобранное значение, и возвращает код выхода0.
ParseResult parseResult = rootCommand.Parse(args);
if (parseResult.Errors.Count == 0 && parseResult.GetValue(fileOption) is FileInfo parsedFile)
{
ReadFile(parsedFile);
return 0;
}
- Если для
--fileне было указано значение, он выводит доступные ошибки синтаксического анализа и возвращает1код выхода:
foreach (ParseError parseError in parseResult.Errors)
{
Console.Error.WriteLine(parseError.Message);
}
return 1;
- Метод
ReadFileсчитывает указанный файл и отображает его содержимое в консоли:
static void ReadFile(FileInfo file)
{
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
}
}
Тестирование приложения
При разработке приложения командной строки вы можете использовать один из следующих способов для тестирования:
dotnet buildВыполните команду, а затем откройте командную строку в папке scl/bin/Debug/net9.0, чтобы запустить исполняемый файл:dotnet build cd bin/Debug/net9.0 scl --file scl.runtimeconfig.jsonИспользуйте
dotnet runи передайте значения параметров в приложение вместо командыrun, включив их после--, как показано в следующем примере:dotnet run -- --file bin/Debug/net9.0/scl.runtimeconfig.json
В этом руководстве предполагается, что вы используете первый из этих вариантов.
При запуске приложения отображается содержимое файла, указанного параметром --file.
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
}
}
}
Но что произойдет, если вы попросите систему отобразить справку, предоставив --help? Ничего не выводится в консоль, так как приложение пока не обрабатывает сценарий, в котором --file не указано, и ошибки синтаксического анализа отсутствуют.
Анализ аргументов и вызов parseResult
System.CommandLine позволяет указать действие, вызываемое при успешном анализе заданного символа (команды, директивы или параметра). Действие — это делегат, который принимает System.CommandLine.ParseResult параметр и возвращает int код выхода (асинхронные действия также доступны). Код выхода возвращается методом System.CommandLine.Parsing.ParseResult.Invoke и может использоваться для указания того, выполнена ли команда успешно или нет.
Замените содержимое файла Program.cs кодом, приведенным ниже.
using System.CommandLine; namespace scl; class Program { static int Main(string[] args) { Option<FileInfo> fileOption = new("--file") { Description = "The file to read and display on the console." }; RootCommand rootCommand = new("Sample app for System.CommandLine"); rootCommand.Options.Add(fileOption); rootCommand.SetAction(parseResult => { FileInfo parsedFile = parseResult.GetValue(fileOption); ReadFile(parsedFile); return 0; }); ParseResult parseResult = rootCommand.Parse(args); return parseResult.Invoke(); } static void ReadFile(FileInfo file) { foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); } } }
Предыдущий код:
Указывает, что
ReadFileэто метод, который будет вызываться при вызове корневой команды:rootCommand.SetAction(parseResult => { FileInfo parsedFile = parseResult.GetValue(fileOption); ReadFile(parsedFile); return 0; });argsАнализирует результат и вызывает результат:ParseResult parseResult = rootCommand.Parse(args); return parseResult.Invoke();
При запуске приложения отображается содержимое файла, указанного параметром --file.
Что произойдет, если вы попросите его отобразить справку, предоставив --help?
scl --help
Следующие выходные данные печатаются:
Description:
Sample app for System.CommandLine
Usage:
scl [options]
Options:
-?, -h, --help Show help and usage information
--version Show version information
--file The file to read and display on the conso
RootCommand по умолчанию предоставляет параметр справки, параметр версии и директиву Suggest. Метод ParseResult.Invoke(InvocationConfiguration) отвечает за вызов действия разобранного символа. Это может быть действие, специально заданное для команды, или действие справки, заданное System.CommandLine для System.CommandLine.Help.HelpOption. Кроме того, при обнаружении ошибок синтаксического анализа он выводит их в стандартную ошибку, печатает стандартные выходные данные и возвращается 1 в качестве кода выхода:
scl --invalid bla
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
Добавление подкоманда и параметров
В этом разделе вы:
- Создайте дополнительные параметры.
- Создайте подкоманда.
- Назначьте новые параметры новой подкоманде.
Новые параметры позволяют настроить цвета переднего плана и фона текста и скорость чтения. Эти возможности будут использоваться для чтения коллекции цитат, поступающих из руководства консольного приложения телеcуфлера.
Скопируйте файл sampleQuotes.txt из репозитория GitHub для этого примера в каталог проекта. Сведения о том, как скачать файлы, см. в инструкциях Samples and Tutorials.
Откройте файл проекта и добавьте элемент
<ItemGroup>непосредственно перед закрывающим тегом</Project>:<ItemGroup> <Content Include="sampleQuotes.txt"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup>Добавление этой разметки приводит к копированию текстового файла в папку bin/debug/net9.0 при сборке приложения. Таким образом, при запуске исполняемого файла в этой папке можно получить доступ к файлу по имени, не указывая путь к папке.
В Program.csпосле кода, создающего параметр
--file, создайте параметры для управления скоростью чтения и цветами текста:Option<int> delayOption = new("--delay") { Description = "Delay between lines, specified as milliseconds per character in a line.", DefaultValueFactory = parseResult => 42 }; Option<ConsoleColor> fgcolorOption = new("--fgcolor") { Description = "Foreground color of text displayed on the console.", DefaultValueFactory = parseResult => ConsoleColor.White }; Option<bool> lightModeOption = new("--light-mode") { Description = "Background color of text displayed on the console: default is black, light mode is white." };После строки, которая создает корневую команду, удалите код, который добавляет параметр
--fileк ней. Вы удаляете его здесь, поскольку добавите его в новую подкоманду.После строки, создающей корневую команду, создайте подкоманду
read. Добавьте параметры в эту подкоманду (с помощью синтаксиса инициализатора коллекции, а не свойстваOptions) и добавьте подкоманду в корневую команду.Command readCommand = new("read", "Read and display the file.") { fileOption, delayOption, fgcolorOption, lightModeOption }; rootCommand.Subcommands.Add(readCommand);Замените код
SetActionследующим кодомSetActionдля нового подкоманда:readCommand.SetAction(parseResult => ReadFile( parseResult.GetValue(fileOption), parseResult.GetValue(delayOption), parseResult.GetValue(fgcolorOption), parseResult.GetValue(lightModeOption)));Вы больше не вызываете
SetActionв корневой команде, потому что корневая команда больше не нуждается в действии. При наличии вложенных команд обычно необходимо указать один из вложенных команд при вызове приложения командной строки.Замените
ReadFileметод действия следующим кодом:internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode) { Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black; Console.ForegroundColor = fgColor; foreach (string line in File.ReadLines(file.FullName)) { Console.WriteLine(line); Thread.Sleep(TimeSpan.FromMilliseconds(delay * line.Length)); } }
Теперь приложение выглядит следующим образом:
using System.CommandLine;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "The file to read and display on the console."
};
Option<int> delayOption = new("--delay")
{
Description = "Delay between lines, specified as milliseconds per character in a line.",
DefaultValueFactory = parseResult => 42
};
Option<ConsoleColor> fgcolorOption = new("--fgcolor")
{
Description = "Foreground color of text displayed on the console.",
DefaultValueFactory = parseResult => ConsoleColor.White
};
Option<bool> lightModeOption = new("--light-mode")
{
Description = "Background color of text displayed on the console: default is black, light mode is white."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
Command readCommand = new("read", "Read and display the file.")
{
fileOption,
delayOption,
fgcolorOption,
lightModeOption
};
rootCommand.Subcommands.Add(readCommand);
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
return rootCommand.Parse(args).Invoke();
}
internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
Thread.Sleep(TimeSpan.FromMilliseconds(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:
-?, -h, --help Show help and usage information
--version Show version 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\net9.0\nofile''.
File name: 'C:\bin\Debug\net9.0\nofile''
Добавление подкоманд и настраиваемой проверки
В этом разделе создается окончательная версия приложения. По завершении приложение будет иметь следующие команды и параметры:
- корневая команда с рекурсивным параметром, именуемым
--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"
В Program.csзамените код, который создает параметр
--fileследующим кодом:Option<FileInfo> fileOption = new("--file") { Description = "An option whose argument is parsed as a FileInfo", Required = true, DefaultValueFactory = result => { if (result.Tokens.Count == 0) { return new FileInfo("sampleQuotes.txt"); } string filePath = result.Tokens.Single().Value; if (!File.Exists(filePath)) { result.AddError("File does not exist"); return null; } else { return new FileInfo(filePath); } } };Этот код использует
System.CommandLine.Parsing.ArgumentResultдля предоставления пользовательского синтаксического анализа, проверки и обработки ошибок.Без этого кода отсутствие файлов сообщается с вызовом исключения и трассировкой стека. В этом коде отображается только указанное сообщение об ошибке.
Этот код также задает значение по умолчанию, поэтому
DefaultValueFactoryустанавливается на пользовательский метод синтаксического анализа.После кода, создающего
lightModeOption, добавьте параметры и аргументы для командaddиdelete.Option<string[]> searchTermsOption = new("--search-terms") { Description = "Strings to search for when deleting entries.", Required = true, AllowMultipleArgumentsPerToken = true }; Argument<string> quoteArgument = new("quote") { Description = "Text of quote." }; Argument<string> bylineArgument = new("byline") { Description = "Byline of quote." };Параметр
xref:System.CommandLine.Option.AllowMultipleArgumentsPerTokenпозволяет опустить имя параметра--search-termsпри указании элементов в списке после первого. В нем приведены следующие примеры эквивалента входных данных командной строки:scl quotes delete --search-terms David "You can do" scl quotes delete --search-terms David --search-terms "You can do"Замените код, который создает корневую команду и команду
readследующим кодом:RootCommand rootCommand = new("Sample app for System.CommandLine"); fileOption.Recursive = true; rootCommand.Options.Add(fileOption); Command quotesCommand = new("quotes", "Work with a file that contains quotes."); rootCommand.Subcommands.Add(quotesCommand); Command readCommand = new("read", "Read and display the file.") { delayOption, fgcolorOption, lightModeOption }; quotesCommand.Subcommands.Add(readCommand); Command deleteCommand = new("delete", "Delete lines from the file."); deleteCommand.Options.Add(searchTermsOption); quotesCommand.Subcommands.Add(deleteCommand); Command addCommand = new("add", "Add an entry to the file."); addCommand.Arguments.Add(quoteArgument); addCommand.Arguments.Add(bylineArgument); addCommand.Aliases.Add("insert"); quotesCommand.Subcommands.Add(addCommand);Этот код вносит следующие изменения:
Удаляет параметр
--fileиз командыread.Добавляет параметр
--fileв качестве рекурсивной опции к корневой команде.Создает команду
quotesи добавляет ее в корневую команду.Добавляет команду
readв командуquotesвместо корневой команды.Создает команды
addиdeleteи добавляет их в командуquotes.
Результатом является следующая иерархия команд:
- Корневая команда
quotesreadadddelete
Теперь приложение реализует рекомендуемый шаблон, в котором родительская команда (
quotes) указывает область или группу, а его дочерние команды (read,add,delete) являются действиями.Рекурсивные параметры применяются к команде и рекурсивно к подкомандам. Так как
--fileнаходится в корневой команде, она будет доступна автоматически во всех подкомандах приложения.После кода
SetActionдобавьте новый кодSetActionдля новых подкоманд.deleteCommand.SetAction(parseResult => DeleteFromFile( parseResult.GetValue(fileOption), parseResult.GetValue(searchTermsOption))); addCommand.SetAction(parseResult => AddToFile( parseResult.GetValue(fileOption), parseResult.GetValue(quoteArgument), parseResult.GetValue(bylineArgument)) );Подкоманда
quotesне имеет действия, поскольку это не команда-узел. Подкомандыread,addиdeleteявляются конечными командами подquotes, и для каждой из них вызываетсяSetAction.Добавьте действия для
addиdelete.internal static void DeleteFromFile(FileInfo file, string[] searchTerms) { Console.WriteLine("Deleting from file"); var lines = File.ReadLines(file.FullName).Where(line => searchTerms.All(s => !line.Contains(s))); File.WriteAllLines(file.FullName, lines); } 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}"); }
Готовое приложение выглядит следующим образом:
using System.CommandLine;
namespace scl;
class Program
{
static int Main(string[] args)
{
Option<FileInfo> fileOption = new("--file")
{
Description = "An option whose argument is parsed as a FileInfo",
Required = true,
DefaultValueFactory = result =>
{
if (result.Tokens.Count == 0)
{
return new FileInfo("sampleQuotes.txt");
}
string filePath = result.Tokens.Single().Value;
if (!File.Exists(filePath))
{
result.AddError("File does not exist");
return null;
}
else
{
return new FileInfo(filePath);
}
}
};
Option<int> delayOption = new("--delay")
{
Description = "Delay between lines, specified as milliseconds per character in a line.",
DefaultValueFactory = parseResult => 42
};
Option<ConsoleColor> fgcolorOption = new("--fgcolor")
{
Description = "Foreground color of text displayed on the console.",
DefaultValueFactory = parseResult => ConsoleColor.White
};
Option<bool> lightModeOption = new("--light-mode")
{
Description = "Background color of text displayed on the console: default is black, light mode is white."
};
Option<string[]> searchTermsOption = new("--search-terms")
{
Description = "Strings to search for when deleting entries.",
Required = true,
AllowMultipleArgumentsPerToken = true
};
Argument<string> quoteArgument = new("quote")
{
Description = "Text of quote."
};
Argument<string> bylineArgument = new("byline")
{
Description = "Byline of quote."
};
RootCommand rootCommand = new("Sample app for System.CommandLine");
fileOption.Recursive = true;
rootCommand.Options.Add(fileOption);
Command quotesCommand = new("quotes", "Work with a file that contains quotes.");
rootCommand.Subcommands.Add(quotesCommand);
Command readCommand = new("read", "Read and display the file.")
{
delayOption,
fgcolorOption,
lightModeOption
};
quotesCommand.Subcommands.Add(readCommand);
Command deleteCommand = new("delete", "Delete lines from the file.");
deleteCommand.Options.Add(searchTermsOption);
quotesCommand.Subcommands.Add(deleteCommand);
Command addCommand = new("add", "Add an entry to the file.");
addCommand.Arguments.Add(quoteArgument);
addCommand.Arguments.Add(bylineArgument);
addCommand.Aliases.Add("insert");
quotesCommand.Subcommands.Add(addCommand);
readCommand.SetAction(parseResult => ReadFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(delayOption),
parseResult.GetValue(fgcolorOption),
parseResult.GetValue(lightModeOption)));
deleteCommand.SetAction(parseResult => DeleteFromFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(searchTermsOption)));
addCommand.SetAction(parseResult => AddToFile(
parseResult.GetValue(fileOption),
parseResult.GetValue(quoteArgument),
parseResult.GetValue(bylineArgument))
);
return rootCommand.Parse(args).Invoke();
}
internal static void ReadFile(FileInfo file, int delay, ConsoleColor fgColor, bool lightMode)
{
Console.BackgroundColor = lightMode ? ConsoleColor.White : ConsoleColor.Black;
Console.ForegroundColor = fgColor;
foreach (string line in File.ReadLines(file.FullName))
{
Console.WriteLine(line);
Thread.Sleep(TimeSpan.FromMilliseconds(delay * line.Length));
}
}
internal static void DeleteFromFile(FileInfo file, string[] searchTerms)
{
Console.WriteLine("Deleting from file");
var lines = File.ReadLines(file.FullName).Where(line => searchTerms.All(s => !line.Contains(s)));
File.WriteAllLines(file.FullName, lines);
}
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}");
}
}
Выполните сборку проекта, а затем выполните следующие команды.
Отправьте несуществующий файл в --file с помощью команды read, и вы получите сообщение об ошибке вместо исключения и трассировки стека:
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/net9.0 , в этой папке будет находиться файл с изменениями из add и delete команд. Копия файла в папке проекта остается неизменной.
Дальнейшие действия
В этом руководстве вы создали простое приложение командной строки, использующее System.CommandLine. Дополнительные сведения о библиотеке см. в System.CommandLine обзоре.