CommandParser - 受 getopt() 启发的 C# 命令行解析器,使用 LINQ






4.92/5 (17投票s)
在创建命令行工具时,命令行参数解析是一个恼人的、重复性的问题。更糟糕的是,它在计算历史的整个过程中已经被解决了许多次。这个库就是试图解决处理命令行参数时遇到的许多常见问题。
引言
C# 的 CommandParser
是一个简单的对象,旨在处理命令行工具创建者在处理命令行参数时会遇到的绝大多数问题。它的灵感来源于 C 运行时函数 "getopt()
",但并不完全复制其所有方面。
我们可以将这些常见问题归类如下:
- 以用户友好的方式解析命令行参数
- 允许程序员轻松指定和处理命令行参数
- 生成一个直接依赖于可用参数的帮助屏幕
- 检测未知或无效的命令
- 检测必需但未提供的命令
当我提到这个项目受到 getopt()
的启发时,这种启发主要体现在用户可以如何提供命令行参数。这个项目的所有其他方面都与 getopt()
大不相同。我反而试图利用 C# 中最新的语言特性来实现类似的效果。我绝没有尝试实现一个完全符合 POSIX 标准的库,因此如果存在任何与该标准的偏差,我深表歉意。
背景
任何熟悉 getopt()
方法的人都应该知道,它做了很多有用的事情,其中最重要的是以灵活的方式处理命令行参数的解析。为了说明这一点,想象一下你有一个程序需要接受 3 个不同的标志,p、q 和 z,并且 z 标志需要一个参数。
使用 getopt
,你可以用以下任何一种方式指定这些命令,它们都将被视为相同:
-p -q -z7
-p -q -z 7
-pqz7
-p -qz7
-p -qz "7"
-p -qz"7"
你可以看到它的灵活性。Getopt
也支持参数的长格式。因此,你可以让 "-p" 选项也带有一个参数,如 "-populate"。这可以使可用的命令更容易记忆和理解。
这一切都很好,但在我看来,getopt()
有一些地方并不那么“opt”(选择),包括:
- 一种神秘但强大的格式,用于指定命令参数
- 没有易于使用的机制来生成支持命令的帮助屏幕
- 使用
getopt
分派命令的机制通常涉及一个笨拙的switch
语句 - 据我所知,没有可用的实现能够为 .NET 程序员解决所有这些问题。
正是因为这些原因,我决定构建这个项目并分享它。希望你觉得它有用。
Using the Code
使用 CommandParser
非常简单。你基本上需要指定解析器将处理的参数,解析命令行,然后处理结果。
指定参数
以下示例显示了一个典型参数规范,用于一个必需的且带有参数的命令:
var parser = new CommandParser("A Sample Test Harness for CommandParser.cs");
parser.Argument("h", "host", "Specify a host ip address", "ip_addr",
CommandArgumentFlags.TakesParameter | CommandArgumentFlags.Required,
(p, v) => {
host = v;
});
在这个例子中,我们向 "Argument
" 方法传递了 6 个参数。第一个参数是标志的短格式,第二个是长格式,第三个是标志的描述,将出现在自动生成的帮助屏幕上,第四个是用于定义该参数在帮助屏幕中接收的值的字符串。
最后,handler 参数描述了当遇到命令标志时应该做什么。这个 handler 是一个函数,它接受一个对正在解析的 CommandParser
的引用,以及一个包含传递给它的参数值的 string
。在上面的例子中,我们使用 C# lambda 表达式在内联定义 handler。由于在实践中,许多命令行参数仅仅是需要存储在工具设置对象上的标志,因此能够与标志的定义一起指定如何处理该标志非常方便。
解析命令行
一旦你指定了所有希望解析器处理的参数,让解析器开始工作的过程就很简单了:
parser.Parse();
当你调用 Parse()
方法时,命令行解析器将确定哪些标志存在,并在遇到它们时,调用在定义参数时提供的操作,如上所述。
在解析过程中,任何检测到的未知命令以及任何必需但未提供的命令都将被解析器对象存储。这让你能够根据你希望如何处理这些问题来采取适当的行动。
处理解析后的结果
在这个项目的包含的测试程序集中,提供了以下代码来展示解析完成后处理程序执行的一种方式。
if (parser.UnknownCommands.Count > 0) {
foreach (var unknown in parser.UnknownCommands) {
Console.WriteLine("Invalid command: " + unknown);
}
Console.WriteLine(parser.GetHelp());
} else if (parser.MissingRequiredCommands.Count > 0) {
foreach (var missing in parser.MissingRequiredCommands) {
Console.WriteLine("ERROR: Missing argument: " + missing);
}
Console.WriteLine(parser.GetHelp());
} else if (!showHelp) {
Console.WriteLine("update = " + update);
Console.WriteLine("populate = " + populate);
Console.WriteLine("host = " + host);
Console.WriteLine("query = " + query);
} else {
Console.WriteLine(parser.GetHelp());
}
在这个例子中,如果我们不显式显示帮助屏幕,那么我们将执行该工具的主要目的。在这种情况下,该工具输出了解析标志的结果,但你的工具可能会做一些更有用的事情。
更有趣的是,我们可以轻松快速地处理未知命令以及缺失的必需命令。我们甚至可以通过简单地调用 parser.GetHelp()
来显示帮助屏幕。
CommandParser
提供的帮助屏幕使用你指定的参数列表,根据你提供给解析器的参数自动生成帮助文本。这意味着当你更新支持的命令列表时,帮助屏幕也会自动更新。
关注点
我很确定还有很多可以改进的地方,使其更加优雅。我仍然不太满意参数规范的处理方式。它确实有效,但我感觉它有点笨拙,可以做得更好。我很乐意听取任何人的建议。
历史
- V1.0 - 初始发布