重要
System.CommandLine
目前为预览版,本文档适用于版本 2.0 beta 5。
某些信息与预发布产品有关,该产品在发布前可能会进行大幅修改。 Microsoft对此处提供的信息不作任何明示或暗示的保证。
本教程演示如何创建使用库的 System.CommandLine
.NET 命令行应用。 首先创建一个具有一个选项的简单根命令。 然后,你将基于该基础构建,创建一个更复杂的应用,其中包含多个子命令和每个命令的不同选项。
本教程中,您将学习如何:
- 创建命令、选项和参数。
- 指定选项的默认值。
- 将选项和参数分配给命令。
- 以递归方式将选项分配给命令下的所有子命令。
- 使用多个级别的嵌套子命令。
- 为命令和选项创建别名。
- 使用
string
、string[]
、int
、bool
和FileInfo
枚举选项类型。 - 读取命令作代码中的选项值。
- 使用自定义代码分析和验证选项。
先决条件
- 最新的 .NET SDK
- Visual Studio Code 编辑器
- C# 开发套件
或
- 安装了 .NET 桌面开发工作负载的 Visual Studio 2022。
创建应用
创建名为“scl”的 .NET 9 控制台应用项目。
为项目创建名为 scl 的文件夹,然后在新文件夹中打开命令提示符。
运行下面的命令:
dotnet new console --framework net9.0
安装 System.CommandLine 包
运行下面的命令:
dotnet add package System.CommandLine --prerelease
或者,在 .NET 10+ 中:
dotnet package add System.CommandLine --prerelease
该
--prerelease
选项是必需的,因为该库仍处于 beta 阶段。
分析参数
将 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.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); } } }
前面的代码:
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.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
System.CommandLine.RootCommand
默认情况下,提供 “帮助”选项、 “版本”选项 和 “建议”指令。
ParseResult.Invoke
方法负责调用已分析符号的作。 可以是为命令显式定义的作,也可以是为其System.CommandLine
System.CommandLine.Help.HelpOption
定义的帮助作。 此外,当它检测到任何分析错误时,它会将它们输出到标准错误,输出有助于标准输出并返回 1
退出代码:
scl --invalid bla
Unrecognized command or argument '--invalid'.
Unrecognized command or argument 'bla'.
添加子命令和选项
本部分的操作:
- 创建更多选项。
- 创建子命令。
- 将新选项分配给新的子命令。
通过新选项,可以配置前景和背景文本颜色以及读出速度。 这些功能将用于读取来自 Teleprompter 控制台应用教程的报价集合。
将此示例的 gitHub 存储库中的sampleQuotes.txt文件复制到项目目录中。 有关如何下载文件的信息,请参阅 示例和教程中的说明。
打开项目文件,并在结束
</Project>
标记前添加元素<ItemGroup>
:<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
运行仅--file
指定选项的子命令read
,并获取其他三个选项的默认值。
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''
添加子命令和自定义验证
本部分将创建应用的最终版本。 完成后,应用将具有以下命令和选项:
- 具有名为 recursive* 选项的 root 命令
--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
代码后,为和命令添加选项和delete
参数add
: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
命令。
结果是以下命令层次结构:
- Root 命令
quotes
read
add
delete
应用现在实现 建议 的模式,其中父命令 (
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}");
}
}
生成项目,然后尝试以下命令。
使用read
命令将不存在的文件提交到--file
其中,你会收到错误消息,而不是异常和堆栈跟踪:
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 概述。