C# 强大的命令行解析库






4.89/5 (100投票s)
2007年8月5日
40分钟阅读

372300

6807
一篇教程,介绍了一个用于 C# 和 .NET 中强大而简洁的命令行参数解析库。
目录
- 0 许可证更新
- 1 简介
- 2 需求
- 3 第一个例子:Hello world!
- 4 指定命令行选项
- 4.1 特性
- 4.1.1 CommandLineManagerAttribute
- 4.1.1.1 描述性参数
- 4.1.1.2 指定启用的选项样式
- 4.1.1.3 选项名称的大小写敏感性
- 4.1.1.4 要求赋值字符或使其可选
- 4.1.2 CommandLineOptionGroupAttribute
- 4.1.2.1 必需参数
- 4.1.2.2 描述性参数
- 4.1.2.3 组要求
- 4.1.2.4 使赋值字符必需或可选
- 4.1.3 CommandLineOptionAttribute
- 4.1.3.1 描述性参数
- 4.1.3.2 将选项添加到选项组
- 4.1.3.3 限制选项可以指定的次数
- 4.1.3.4 限制数值类型的允许值范围
- 4.1.3.5 为选项指定别名
- 4.1.3.6 禁止特定选项一起指定
- 4.1.3.7 布尔选项的特殊处理
- 4.1.3.8 使赋值字符必需或可选
- 4.1.3.9 提供默认值
- 4.1.1 CommandLineManagerAttribute
- 4.2 选项样式
- 4.3 支持的成员类型
- 4.4 更高级的示例
- 4.4.1 一个简单的 'tar'
- 4.1 特性
- 5 使用命令行解析器
- 5.1 构造解析器
- 5.2 解析命令行
- 5.3 错误处理
- 5.4 生成用法信息
- 5.5 国际化 (i18n)
- 6 高级自定义
- 7 实现细节 (简述)
- 8 结语
0 许可证更新
应用于此库的许可证并未经过认真考虑,并且不再被认为适合此库。因此,决定更改许可证。此库的新许可证如下:
Copyright (c) 2007 Peter Palotas
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
源代码尚未更新以反映这一点,但任何人都可以将此新许可证应用于源代码。完整的 Plossum
库将很快更新以反映此更改。
1 简介
1.1 概述
当今编写的许多应用程序都会接受某种形式的命令行参数。有时我们只需要解析命令行以获取在应用程序启动时要打开的文件名,有时我们需要处理大量各种选项来控制应用程序的执行方式。我们中的大多数人在职业生涯的某个阶段都编写过一些代码来处理传递给应用程序的命令行参数。这项工作很繁琐,而且很容易出错,因为我们忘记验证某些选项或值的组合,从而导致进一步执行应用程序时出错。
存在一些库可以执行命令行解析,并允许我们以一种(有时不那么)简单的方式检索指定的选项。在 UN*X 环境中,命令行参数通常被大量使用,有一个名为 getopt
[2] 的库。正如我最近发现的那样,C# 中也有一个此库的端口,名为“GNU Getopt .NET”[5]。Code Project 上也有一些项目。但是,要么现有的库不够功能齐全,要么我就是不喜欢它们的使用语法,所以我决定自己编写一个命令行解析器。本文以教程的形式介绍了我创建的这个库,并重点介绍其大部分功能。
1.2 背景
如何为程序指定命令行选项(或开关)的格式在不同系统和应用程序之间有所不同。在 UN*X 系统中,传统方式是使用由 -
(连字符)引入的单字符开关;例如 ls -l -F -a
。这类选项通常被称为“标志”。多个标志也可以组合或分组为一个,所以上面的例子可以重写为 ls -lFa
。还有另一种指定选项的方法,即使用 GNU 的“长选项”,它们由 --
引入,通常是一个或多个完整的单词,例如 ls --long
--classify --all
。长选项的参数通常使用 =
提供,例如 ls
--block-size=1024
。也有一些程序使用单连字符的长选项,例如 mplayer -nosound
。GNU 的 --
也用于终止选项列表,如果您需要指定一个以连字符开头的例如文件名,这会很有用。
在 MS-DOS 中,选项传统上是由 /
斜杠引入的单字符,例如 dir
/w /p /a:s
。这里的冒号字符与上面示例中的 =
作用相同。有关命令行参数的历史和传统的更多信息,请参阅 [1]。
如今,这些样式被大量混合使用,许多 Windows 程序现在也接受以单连字符为前缀的选项,并且长短选项的参数通常可以在不使用 =
或 :
字符的情况下赋值。我想要一个能够支持所有这些样式并且可配置以允许哪些样式以及如何解释它们的库。
此外,我希望避免编写笨拙的代码来指定允许和必需的命令行选项,以检索和验证提供的参数等。我还希望用法信息能够从命令行选项的规范中自动生成用于显示在帮助文本中。Code Project 上一篇题为“C# 中的自动命令行解析”[3] 的文章使用了一种巧妙的方式,通过 .NET 特性来指定程序可用的选项。这是一种非常清晰、易于理解、阅读和管理的指定可用选项以及每个选项各种参数的方式。但是,该文章中提供的解析器缺乏我想要的许多功能,所以我决定采用使用特性来指定可用命令行选项的策略,并创建了我自己的命令行参数解析库;Plossum.CommandLine
。
2 需求
此命令行库是用 C# 为 .NET 2.0 Framework 编写的。它依赖于“C5 C# 和 CLI 的通用集合库”[4],这是一个很棒的库,在我看来,它应该集成到 .NET Framework 中。
3 第一个例子:Hello world!
让我们从一个非常简单的应用程序示例开始,该应用程序只接受两个选项;-help
和 -name
。如果指定了 help
,则应该将描述程序的帮助文本打印到控制台。如果未指定 help
,则必须指定 name
选项,并且应该提供运行程序的用户的姓名。在这个非常简单的例子中,不使用 name
选项而直接提供姓名可能更直观,但我们在此处使用该选项是为了说明目的。
此库中的所有类都可在 Plossum.CommandLine
命名空间中使用。
首先要做的是创建一个我们称之为“命令行管理器”的类。此类用于指定命令行解析器可用的选项,并且该对象的实例将在运行命令行解析器时接收命令行中指定的选项值。
列表 3.1:命令行管理器
[CommandLineManager(ApplicationName="Hello World",
Copyright="Copyright (c) Peter Palotas")]
class Options
{
[CommandLineOption(Description="Displays this help text")]
public bool Help = false;
[CommandLineOption(Description = "Specifies the input file", MinOccurs=1)]
public string Name
{
get { return mName; }
set
{
if (String.IsNullOrEmpty(value))
throw new InvalidOptionValueException(
"The name must not be empty", false);
mName = value;
}
}
private string mName;
}
上面的代码说明了 Plossum.CommandLine
命名空间中定义的两个(三个可用)特性,并让您初步了解使用此特性技术指定命令行选项是多么简单和清晰。CommandLineManager
特性指示此对象是一个命令行管理器,并允许您选择指定对整个命令行解析全局的更多选项。在此示例中,我们指定了应用程序的名称和版权消息。这些字符串稍后将包含在命令行解析器生成的格式化消息中。如果我们没有指定这些选项,它们将默认为最初加载的可执行文件所在的程序集的 AssemblyTitle
和 AssemblyCopyright
特性中指定的值。
该类包含一个 public
字段(是的,这本应是一个属性而不是 public
字段,如果这是一个实际的应用程序,但为了简洁起见,我们在这里使用 public
字段)名为 Help
,以及一个公共属性名为 Name
。这些都应用了 CommandLineOption
特性。这表明这些成员将被用作命令行选项。Description
参数用于指定每个选项的帮助文本,可以在构造解析器后显示。每个选项的名称都可以通过 Name
参数显式指定,但如果未指定,则使用成员的名称(在本例中为 Name
和 Help
)。我们还注意到,如果值是 null
或空,Name
属性的 setter 方法会抛出 InvalidOptionValueException
类型的异常(解析器永远不会将其传递为 null
引用,但它可能是一个空字符串)。从成员在被解析器设置值时抛出此异常,将导致解析器捕获异常并识别并稍后报告该错误。
为 Name
选项指定的 MinOccurs
参数表示必须在命令行中提供此选项的最小次数。对于任何非数组或非集合类型,此参数必须是 0
或 1
。在这里,我们指定 Name
选项必须出现至少一次,这意味着指定它是强制性的。
列表 3.2:Hello World - Main 方法
class Program
{
static int Main(string[] args)
{
Options options = new Options();
CommandLineParser parser = new CommandLineParser(options);
parser.Parse();
Console.WriteLine(parser.UsageInfo.GetHeaderAsString(78));
if (options.Help)
{
Console.WriteLine(parser.UsageInfo.GetOptionsAsString(78));
return 0;
}
else if (parser.HasErrors)
{
Console.WriteLine(parser.UsageInfo.GetErrorsAsString(78));
return -1;
}
Console.WriteLine("Hello {0}!", options.Name);
return 0;
}
}
我们首先创建一个命令行管理器类 Options
的实例,然后创建一个 CommandLineParser
的新实例,该类执行实际的命令行解析,并将我们新创建的 Options
对象传递给它。创建 CommandLineParser
时,它会使用反射检查传递给它的对象,以创建可用选项的内部表示,并验证所有指定的参数是否正确。如果此处有任何不正确之处,将抛出 AttributeException
类型的异常,指示错误。我们在这里不捕获此类异常,因为此类异常表示编程错误,在应用程序部署时应不出现。
然后我们调用 CommandLineParser
的 Parse()
方法。这执行实际的命令行解析,并设置我们 Options
实例中被指定为命令行选项的任何值。
对 parser.UsageInfo.GetHeaderAsString(78)
的调用返回一个包含应用程序典型“标题”的字符串,该标题由应用程序名称、版本号和版权消息组成,其中一些是我们使用上一个列表中的参数设置的。参数 78
表示返回字符串的最大宽度,并且字符串将在该宽度内使用单词换行进行拟合。
然后我们检查 Help
选项是否被指定,因为如果被指定了,我们的命令行管理器对象的 Help
成员将被设置为 true
。如果 Help
未在命令行中指定,则该字段根本不会被解析器设置,因此它仍将是 false
的值,因为布尔字段默认为 false
。如果 Help
选项被指定,我们打印 parser.UsageInfo.GetOptionsAsString(78)
返回的字符串。同样,参数表示拟合字符串的字段宽度。调用的方法返回一个列出程序所有可用选项及其描述的字符串,这些描述由 CommandLineOptionAttribute
的 Description
参数指定。
如果未指定 Help
选项,我们则检查命令行中是否有任何错误。之所以在指定 Help
时不对其进行检查,是因为解析器会为丢失的选项产生错误,如果只指定 Help
选项,Name
选项将丢失,因此会产生错误。parser.UsageInfo.GetErrorsAsString(78)
返回一个字符串,同样在指定宽度内,列出命令行中找到的错误。请注意,如果任何属性的 set
方法在被解析器调用时抛出 InvalidOptionValueException
,此列表也将包含这些错误。
如果没有错误,我们就知道 Name
选项已被指定且不为空,因此我们在控制台打印一条消息,说“Hello”。
列表 3.3:ex1.exe 没有任何参数
> ex1.exe
Hello World version 1.0.0.0
Copyright (c) Peter Palotas
Errors:
* Missing required option "Name".
从这次运行中我们可以看到,向应用程序提供任何参数都会导致错误消息,说明必须指定 Name
选项。
列表 3.4:ex1.exe 使用 /Help 参数
> ex1.exe /Help
Hello World version 1.0.0.0
Copyright (c) Peter Palotas
Options:
-Help Displays this help text
-Name Specifies the input file
Help
选项会显示可用的选项及其描述。这里我们使用了 Windows 风格来指定选项,但 Unix 风格也可以工作,正如我们在下一次测试中可以看到的。
列表 3.5:ex1.exe 使用 -Name 参数
> ex1.exe -Name=Peter Palotas
Hello World version 1.0.0.0
Copyright (c) Peter Palotas
Hello Peter!
这里我们看到了一些我们可能没有预料到的。最后一个名字,Palotas,没有输出。原因是值和选项是用空格分隔的,这意味着分配给 Name
的唯一内容是“Peter”。那么“Palotas”去哪了?我们没有看到任何错误消息。任何不是选项(由允许的字符之一,在此例中是 /
或 -
)引入的参数都被视为附加参数,可以通过检查 CommandLineParser.RemainingArguments
集合来检索。由于我们的程序不检查这一点,附加参数将被简单地忽略。那么如果我们想向应用程序指定一个包含空格的值(如我的名字)怎么办?答案是我们必须将其放在双引号中(可以指定其他引用方法,我们将在本教程的后续部分进行解释)。所以
列表 3.6:ex1.exe 使用带引号的参数
> ex1.exe -Name="Peter Palotas"
Hello World version 1.0.0.0
Copyright (c) Peter Palotas
Hello Peter Palotas!
...得到了我们想要的结果。
列表 3.7:ex1.exe 使用错误的参数
> ex1.exe -n=23 -Name=""
Hello World version 1.0.0.0
Copyright (c) Peter Palotas
Errors:
* The name must not be empty
* Unknown option "n"
这次运行显示了 Name
的 setter 抛出 InvalidOptionValueException
以及在命令行上指定未定义选项的结果。请随意尝试此程序的各种选项组合并检查结果。
在这里,我们展示了识别选项的最简单的例子之一,它清楚地说明了使用 Plossum
命令行解析器的简单性。但是,它并没有展示多少可用的自定义功能,也没有展示这个库真正有多强大。这将在本教程的接下来的几节中展示。
4 指定命令行选项
4.1 特性
指定可用的命令行选项、描述它们的文本以及定义它们如何工作的属性,是通过使用特性来完成的,如您在第 3 节中所见。Plossum 命令行库提供了三个特性;CommandLineManagerAttribute
、CommandLineOptionAttribute
和 CommandLineOptionGroupAttribute
。
4.1.1 CommandLineManagerAttribute
此特性应用于将用作命令行管理器的类,即定义程序可用命令行特性的类,以及定义将在命令行中指定的选项值接收对象。
4.1.1.1 描述性参数
此特性包含四个描述性属性;ApplicationName
、Copyright
、Version
和 Description
。所有这些特性都默认为运行的可执行文件所在的程序集中定义的相应特性,因此通常不需要在此特性中显式设置它们。
4.1.1.2 指定启用的选项样式
此特性还允许您指定解析器应识别的选项样式。这是通过 EnabledOptionStyles
属性完成的,该属性可以设置为 OptionStyles
枚举中的任何标志组合。有关更多信息,请参阅 4.2。
4.1.1.3 选项名称的大小写敏感性
您可以通过 IsCaseSensitive
属性选择是否区分选项名称的大小写。默认情况下,选项是不区分大小写的。
4.1.1.4 要求赋值字符或使其可选
最后,RequireExplicitAssignment
参数指示是否必须使用可用的赋值字符为选项赋值。如果设置为 false
(默认值),则也可以通过将值与选项名称用空格分隔来为选项赋值,例如 "ex1.exe /name Peter"
。如果设置为 true
,则上面的示例将生成错误:“missing value for option 'name"
”,因为未指定赋值字符。因此,将此设置为 true
要求所有值都必须使用可用的赋值字符赋值,例如 "ex1.exe /name:Peter"
。请注意,此参数也适用于选项组和选项本身,并且在此特性中设置为特定值仅作为任何指定选项的默认值,并且可以根据需要由单个选项或整个选项组覆盖。
4.1.2 CommandLineOptionGroupAttribute
选项组是选项的逻辑分组。这主要有两个目的。一个目的是将选项分组为包含功能相似的选项的组,因为当从命令行解析器检索用法文本时,选项也会根据指定的组进行分组。CommandLineOptionGroupAttribute
只能应用于类,并且只能应用于也具有 CommandLineManager
特性的类。
4.1.2.1 必需参数
此特性的 Id
属性是必需的,并在构造函数中设置。它在单个命令行管理器类定义的选项组中必须是唯一的。它用于选项引用此组,请参阅 4.1.3.2。
4.1.2.2 描述性参数
该组允许您指定一个 Name
和一个 Description
,它们将作为用法描述文本中包含在该组中的选项的标题。如果未指定 Name
参数,则默认使用组的 Id
。
4.1.2.3 组要求
另一个目的是对选项组设置出现要求,例如要求必须指定组中的至少一个选项。这是通过 Require
参数完成的,该参数是 OptionGroupRequirement
类型的枚举。此枚举包含以下特性及其指定含义:
无
这是默认值,表示对该组中的选项没有限制(除了为每个选项明确指定的限制)。AtMostOne
这表示在该组包含的选项中最多必须指定一个。这意味着该组中的选项可以为零个或一个,但不能更多。AtLeastOne
这表示在该组包含的选项中至少必须指定一个,即必须指定该组中的一个或多个选项。ExactlyOne
这表示在该组中必须指定恰好一个选项,不多不少。全部
表示必须在命令行中指定该组的所有选项。
4.1.2.4 使赋值字符必需或可选
与 CommandLineManagerAttribute
一样,此特性包含一个名为 RequireExplicitAssignment
的参数。其功能与 CommandLineManagerAttribute
的功能相同,只是它仅影响属于此组的选项。单个选项仍然可以选择覆盖此设置。有关更多信息,请参阅 4.1.1.4。
4.1.3 CommandLineOptionAttribute
此特性可以应用于带有 CommandLineManagerAttribute
标记的类的字段、属性和方法。但是,对于可以应用它的成员类型有限制,请参阅 4.3。
4.1.3.1 描述性参数
此特性只有两个描述性属性,即 Name
和 Description
。Name
属性表示选项在命令行中应指定的名称。如果未显式指定此属性,则选项的名称将与应用特性的成员的名称相同。Description
属性指定一个描述,该描述将与选项名称一起列在命令行解析器生成的用法描述中。
4.1.3.2 将选项添加到选项组
GroupId
属性可以设置为选项组的 id,并将此选项作为该组的成员。有关更多信息,请参阅 4.1.2。默认值为 null
,表示此选项不属于任何组。
4.1.3.3 限制选项可以指定的次数
MinOccurs
和 MaxOccurs
参数指定选项的基数,即选项可以在命令行中指定(或必须指定)的次数。MinOccurs
确定选项必须指定的最小次数,MaxOccurs
确定选项可以指定的最多次数。如果将 MaxOccurs
设置为零,则表示命令行中对选项可以指定的次数没有上限。请注意,对于任何非数组或非集合成员(方法除外),MinOccurs
必须设置为 0
或 1
,默认为 0
,而 MaxOccurs
必须设置为 1
。
4.1.3.4 限制数值类型的允许值范围
MinValue
和 MaxValue
参数仅适用于数值类型,并分别指定选项允许的最小值和最大值。如果设置为 null
,它们将分别默认为应用特性的成员类型的最大值或最小值。两者参数的默认值均为 null
。
4.1.3.5 为选项指定别名
Aliases
属性名称是此选项的别名,即此选项可以引用的其他名称。这是一个字符串属性,多个名称之间用空格或逗号分隔。这对于指定例如短名称的别名的长选项名称等很有用。此处指定的任何别名在当前命令行管理器中的别名和选项名称之间必须是唯一的。
4.1.3.6 禁止特定选项一起指定
应用程序可用的某些选项可能是互斥的,这意味着它们不能同时在命令行中指定。根据您的需求,可以使用此库的两种方法来实现此目的。一种方法已在 4.1.2.3 中介绍。另一种方法是使用 CommandLineOptionAttribute
的 Prohibits
参数。此参数接受一个逗号分隔的选项名称字符串,这些名称是当前选项所禁止的。这意味着,一旦在命令行中指定了此选项,则列表中禁止的选项也不能出现。如果选项 a
被选项 b
禁止,则选项 b
也被选项 a
隐式禁止,因此无需显式指定(尽管您可以选择这样做)。
4.1.3.7 布尔选项的特殊处理
布尔选项的解释方式及其值的设置可以通过 BoolFunction
参数指定。此参数接受 BoolFunction
类型的枚举。以下值及其各自的含义可用:
Value
这使布尔选项的行为与其他任何选项一样,意味着它期望(并需要)在命令行上为其赋值。值必须是Bool.TrueString
或Bool.FalseString
,不区分大小写。TrueIfPresent
这是布尔选项的默认行为。这意味着该选项在命令行上不接受值。相反,如果该选项存在于命令行中,则应用了定义该选项的特性的成员的值将被设置为true
。像往常一样,如果该选项未在命令行中指定,则CommandLineParser
根本不会设置该成员的值。FalseIfPresent
这使选项的行为与指定TrueIfPresent
相同,不同之处在于,如果选项出现在命令行中,它会将应用了该特性的成员的值设置为false
。UsePrefix
此标志仅在指定了Group
选项样式时有效(请参阅 4.2)。这意味着该选项不接受值,而是引入该选项的字符(前缀)将决定该选项的值。如果使用连字符(-
)指定该选项,则该选项设置为false
,如果使用加号(+
)指定该选项,则该选项设置为true
。
4.1.3.8 使赋值字符必需或可选
此特性还包含 RequireExplicitAssignment
参数,该参数会覆盖组或所属管理器中指定的设置。它的含义与 4.1.1.4 中所述的相同。
4.1.3.9 提供默认值
提供选项的正常默认值,即如果该选项未在命令行中指定,则应用此特性的成员应接收的值,是通过在管理器类的构造函数中初始化成员所需的值来完成的。但是,此特性有一个名为 DefaultAssignmentValue
的参数,该参数接受一个字符串,该字符串包含可解析的成员类型值,或者接受一个与成员类型相同的类型的值。将此参数设置为非 null
引用以外的任何值,允许该选项在不为其赋值的情况下指定。如果该选项在命令行中未赋值,则该选项将被设置为在此参数中指定的值。请注意,如果该选项未在命令行中指定,则该成员仍不会被解析器设置,此值将无意义。另请注意,设置此参数不会禁止或阻止用户在命令行中指定其他值,它仅在用户选择不提供值时提供默认值。
此参数只能设置为非 null
引用(表示没有默认值)以外的值,前提是 RequireExplicitAssignment
(请参阅 4.1.3.8)设置为 true
。
4.2 选项样式
如第 1 节所述,有几种“样式”用于在命令行上引入选项和指定属于这些选项的值。Plossum
命令行库通过使用 CommandLineManagerAttribute
的 EnabledOptionStyles
参数和 OptionStyles
标志枚举来支持这些样式的多种组合。此枚举包含描述多种样式的标志,并可以在 EnabledOptionStyles
属性中指定,以精确选择解析器允许和识别的样式。
可用的样式有:
None
表示未启用样式。解析器将在遇到选项结束开关(--
)时自动设置为此样式。Windows
这表示选项的“Windows 样式”,意味着选项在命令行中使用正斜杠(/
)引入。选项名称长度为一个或多个字符,不能分组(见下文)。默认情况下,这些选项的值可以使用冒号(:
)、等号(=
)字符,或者通过将选项名称与值用空格分隔来赋值(除非明确禁止)。此标志是默认指定的。LongUnix
这表示“GNU longopt 样式”选项,意味着选项在命令行中使用双连字符(--
)引入。选项名称长度为一个或多个字符,从不分组(见下文)。默认情况下,这些选项的值可以使用等号(=)字符,或者通过将选项名称与值用空格分隔来赋值(除非明确禁止)。ShortUnix
这表示“短名称 UNIX 样式”选项,意味着选项在命令行中使用单连字符(-
)引入。选项名称长度为一个或多个字符,具体取决于是否也指定了Group
标志。如果也指定了Group
标志,则由单连字符引入的选项名称只能是单个字符长,否则它们也可以包含任意数量的字符。这些选项的值的赋值方式与LongUnix
相同(见上文)。此标志是默认指定的。文件
这允许在命令行中指定所谓的“列表文件”或“选项文件”。这些文件通过@
字符后跟文件名引入。如果启用了此样式,并且在命令行中遇到了这样的“选项”,命令行解析器将打开该文件并扫描该文件内容以查找更多选项或其他参数,遵循与命令行中指定的选项相同的约定。此标志是默认指定的。Plus
此标志包含ShortUnix
标志,意味着启用此标志也启用了ShortUnix
标志。它允许短 UNIX 样式选项前面可以加上连字符(-
)或加号(+
)字符。这仅对布尔值有意义,并允许前缀(连字符或加号)指定布尔选项将被设置的值。组
此标志包含ShortUnix
标志,意味着启用此标志也启用了ShortUnix
标志。它允许对“短名称 UNIX 样式”中指定的选项进行分组,这意味着不是写例如"tar -x
-v -z -f file.tar.gz"
在命令行上,您可以将任何单字符选项分组,并写成"tar -xvzf file.tar.gz"
,并将以相同方式解释。请注意,分组仅适用于使用“短名称 UNIX 样式”(如果也启用了该标志,则前面加上连字符或加号)指定的选项,并且会阻止您使用单连字符指定任何多字符名称。Unix
这是一个组合标志,仅为方便起见提供,等同于同时指定ShortUnix
和LongUnix
。全部
这是所有可用样式的组合。
这些标志可以自由地使用逻辑 or
运算符组合,以指定您想要允许用户使用的确切样式来指定选项。唯一的限制是,当任何包含它的标志也被指定时,您也不能使用逻辑 and
来移除 ShortUnix
标志。
4.3 支持的成员类型
简而言之,CommandLineOptionAttribute
只能应用于内置类型(除了 object
)、这些类型的数组、System.Collections.ICollection
、System.Collections.Generic.ICollection<T>
或 C5.ICollection<T>
,其中 T
是这些内置类型之一。支持的内置类型有:
bool
byte
sbyte
char
decimal
double
float
int
uint
long
ulong
short
ushort
字符串
enum
该特性可以应用于其中一种类型的公共字段,或其中一种类型的读/写属性。当命令行解析器解析相应选项时,只要指定的值合法,该特性应用的字段或属性将被设置为命令行指定的值。
该特性还可以应用于接受正好一个参数的公共方法,该参数是内置类型之一,而不是数组类型或集合类型。在这种情况下,每当解析器找到与特性匹配的选项时,将调用该方法,并将找到的值传递给该方法。这允许与数组和集合提供的处理方式不同,当多个同名选项在命令行中指定时,这可能很受欢迎。
如果该特性应用于数组或集合,命令行中指定的任何选项的值将被追加到数组或集合中。这提供了一种简单的方式来处理指定多个同名选项。
如果该特性应用于枚举,则为选项指定的值必须匹配枚举中定义的枚举名称,不区分大小写。这意味着使用两个仅因大小写不同而有差异的枚举成员是不允许的,并且将在 CommandLineParser
的构造函数中生成异常。(标识符仅因大小写不同而有差异 anyway 不推荐,因为并非所有 .NET 语言都区分大小写)。
如果方法或属性 setter 在解析器尝试为其分配值时抛出 InvalidOptionValueException
类型的异常,则解析器将捕获此异常并将其转换为一个错误,以后可以以标准方式检索该错误。当 Plossum 库提供的默认验证不足时,这对于以方便的方式验证值很有用。请参阅第 5.3 章。
4.4 更高级的示例
4.4.1 一个简单的 'tar'
下面的列表是一个简单的实现,用于解析一个类似于 UNIX 'tar' 工具的实用程序的命令行。它演示了别名、组要求、选项分组、要求显式赋值以及使用集合存储值的用法。
列表 4.1:一个简单的 'tar'
namespace ex2 {
[CommandLineManager(ApplicationName="Example 2",
Copyright="Copyright (C) Peter Palotas 2007",
EnabledOptionStyles=OptionStyles.Group | OptionStyles.LongUnix)]
[CommandLineOptionGroup("commands", Name="Commands",
Require=OptionGroupRequirement.ExactlyOne)]
[CommandLineOptionGroup("options", Name="Options")]
class Options
{
[CommandLineOption(Name="filter", RequireExplicitAssignment=true,
Description="Specifies a filter on which files to include or exclude",
GroupId="options")]
public List<string> Filters
{
get { return mFilters; }
set { mFilters = value; }
}
[CommandLineOption(Name="v", Aliases="verbose",
Description="Produce verbose output", GroupId="options")]
public bool Verbose
{
get { return mVerbose; }
set { mVerbose = value; }
}
[CommandLineOption(Name="z", Aliases="use-compression",
Description="Compress or decompress the archive", GroupId="options")]
public bool UseCompression
{
get { return mUseCompression; }
set { mUseCompression = value; }
}
[CommandLineOption(Name="c", Aliases="create",
Description="Create a new archive", GroupId="commands")]
public bool Create
{
get { return mCreate; }
set { mCreate = value; }
}
[CommandLineOption(Name = "x", Aliases = "extract",
Description = "Extract files from archive", GroupId = "commands")]
public bool Extract
{
get { return mExtract;}
set { mExtract = value;}
}
[CommandLineOption(Name="f", Aliases="file",
Description="Specify the file name of the archive", MinOccurs=1)]
public string FileName
{
get { return mFileName;}
set { mFileName = value;}
}
[CommandLineOption(Name="h", Aliases="help",
Description="Shows this help text", GroupId="commands")]
public bool Help
{
get { return mHelp; }
set { mHelp = value; }
}
private bool mHelp;
private List<string> mFilters = new List<string>();
private bool mCreate;
private bool mExtract;
private string mFileName;
private bool mUseCompression;
private bool mVerbose;
}
class Program
{
static int Main(string[] args)
{
Options options = new Options();
CommandLineParser parser = new CommandLineParser(options);
parser.Parse();
if (options.Help)
{
Console.WriteLine(parser.UsageInfo.ToString(78, false));
return 0;
}
else if (parser.HasErrors)
{
Console.WriteLine(parser.UsageInfo.ToString(78, true));
return -1;
}
// No errors present and all arguments correct
// Do work according to arguments
return 0;
}
}
}
5 使用命令行解析器
5.1 构造解析器
Plossum.CommandLine.CommandLineParser
是用于执行命令行解析的类,正如您在前面的示例中已经看到的。其构造函数接受对充当命令行管理器的对象的引用,即用各种命令行特性标记的对象以及将存储命令行参数值的对象。它还可以选择性地接受一个 NumberFormatInfo
参数,该参数定义如何解析任何数值。建议指定此项,因为否则您可能不知道数字将如何被解析。例如,我住在瑞典,我们使用逗号(,
)作为小数点分隔符,而不是编程语言通常使用的点(.
),所以如果我不指定数字格式,它会期望我输入以逗号为小数分隔符的十进制值,这在其他方面没有本地化为瑞典的程序中我并不习惯。
构造函数将解析传递给它的对象以查找应用于它的任何命令行特性,并验证指定参数和应用特性的成员的一致性。如果检测到错误,将抛出 AttributeException
类型的异常。此异常派生自 LogicException
,它指示编程错误而不是使用错误。当您的程序完成时,不应抛出此类异常。
5.2 解析命令行
实际解析由命令行解析器的 Parse()
方法完成。有几种此方法的重载,接受包含要解析的命令行的各种输入。这可能是一个字符串、字符串数组或 TextReader
。不接受任何参数的默认 Parse()
方法使用 Environment.CommandLine
中的命令行作为其输入。这些方法还接受一个附加参数 containsExecutable
,该参数指示命令行上的第一个参数实际上是运行的可执行文件的路径。通常是这种情况,但如果您传递一个不包含此参数的输入,则应将其设置为 false
。如果未指定,则它假定可执行文件存在于命令行中。如果可执行文件存在于命令行中,则稍后可以从命令行解析器的 ExecutablePath
属性中检索它。如果没有可执行文件,则此属性将是一个 null 引用。
5.3 错误处理
解析完成后,您可以检查命令行解析器的 HasErrors
属性,该属性指示在解析过程中是否发现了任何错误。这包括由命令行管理器对象中的任何 setter 抛出的 InvalidOptionValueException
指示的任何自定义验证错误。实际错误表示为 ErrorInfo
类型的对象,并且可以从只读集合 Errors
属性中检索。也可以从 UsageInfo
属性(请参阅第 5.4 节)可用的用法信息对象中打印错误。
如果没有错误,则所有已处理的选项都触发了命令行管理器中的一个 setter,现在其所有成员都应设置为反映命令行中传递的选项的值。命令行中的任何剩余参数,即那些不是选项或选项值的参数,都可以从命令行解析器的 RemainingArguments
集合中检索。这通常是输入文件或类似内容,但可以是您想要的任何内容。您需要手动验证这些,因为命令行解析器不会以任何方式自动进行验证。
ErrorInfo
类包含一个指示错误类型的枚举值和一个描述错误的错误消息。如果命令行中指定了列表文件或选项文件并且在解析器中启用了 OptionStyles.File
标志,它还可能包含从该文件解析选项的文件名和行号。有关更多信息,请参阅 API 文档。
5.4 生成用法信息
命令行解析器的 UsageInfo
属性返回一个 UsageInfo
类型的对象。此对象包含应用于命令行管理器的所有命令行特性的描述性信息,并可用于检索包含此信息的格式化字符串。它还可以用于编程设置此信息,这对于国际化(请参阅第 5.5 节)很有用。它包含以下方法,可用于检索格式化字符串:
GetHeaderAsString
此方法在一个指定宽度的字段中返回一个格式化的字符串,包含典型的程序标题,由应用程序名称、版本号和版权声明组成。这通常是任何控制台程序输出的第一项。GetOptionsAsString
此方法返回一个字符串,其中包含程序所有可用选项的列表,格式化为两列(其宽度可以单独指定)。选项根据其组进行分组,并且还包括每个选项组的描述性信息。GetErrorsAsString
此方法返回一个格式化的列表,其中包含用于创建此实例的命令行解析器可以从Errors
属性中获取的所有错误。ToString
此方法返回一个字符串,该字符串是上述三个方法的返回字符串的串联。错误信息可以根据需要省略(例如,在响应程序的帮助选项时),方法是向此方法提供一个布尔参数。
选项和组也通过 OptionInfo
和 OptionGroupInfo
类单独暴露,这些类也公开了这些项目的描述性信息,用于设置和检索格式化字符串。有关更多详细信息,请参阅 API 文档。
5.5 国际化 (i18n)
Plossum
库在设计时就考虑了消息的国际化。目前它只包含 en-US 区域设置的字符串,但希望将来会添加更多。(如果您想翻译这些字符串,请告诉我,因为我只会翻译成瑞典语)。然而,虽然在特性中指定描述和名称简单、易于管理和理解,但在国际化方面还有待改进。据我所知,没有办法将特性的参数设置为资源文件中定义的字符串,这使得描述等的国际化成为一个问题。为了解决这个问题,这些描述性属性需要以编程方式设置,这可以在 UsageInfo
对象中完成,该对象在第 5.4 节中进行了描述。API 文档提供了通过此对象设置描述性信息的可用属性列表。
Plossum
库的所有字符串都使用 CultureInfo.CurrentUICulture
中的区域设置信息进行格式化。
6 高级自定义
解析器仍然可以进行一些更高级的自定义,例如不同选项样式使用的赋值字符、用于引用值的引号,以及在这些引号中可以转义的字符。这些选项的默认设置应该适合大多数应用程序,但如果您需要,它们也可以为您提供自定义。
6.1 指定赋值字符
CommandLineParser
类的 AddAssignmentCharacter
、RemoveAssignmentCharacter
和 ClearAssignmentCharacters
这三个方法为您提供了一种指定哪些字符可用于为选项赋值的方式。连同赋值字符一起,您还为哪个选项样式识别此赋值字符。默认情况下,可用的赋值字符是等号(=
)用于任何接受值的选项,对于 Windows 样式选项,还可以使用冒号(:
)。如果您想完全更改此行为,则应先调用 ClearAssignmentCharacters
,然后再添加您想要提供的样式,以删除任何现有的默认定义。
请注意,除非 RequireExplicitAssignment
设置为 true
,否则空格分隔符始终可用作赋值。
6.2 指定引号和转义序列
为了能够指定包含例如空格的值,命令行解析器必须提供一种引用值或转义单个字符的方法。默认情况下,双引号("
)是唯一被识别的引号。在引号内,您可以转义引号字符本身以在值中插入文字引号,您还可以转义转义字符本身,反斜杠(\
)。例如:"ex1.exe
-name="This is a \"quoted\" value""
这将为 name 选项赋值 This is a "quoted" value
。转义字符也可用于未加引号的值。在那里可以转义的字符只有空格字符和转义字符本身,所以你可以写例如 "ex1.exe -name=Peter\ Palotas"
,名字 Peter Palotas
将被赋给 name 选项。这是默认行为,再次适合大多数应用程序,但如果您有其他要求,可以更改它。
AddQuotation
、RemoveQuotation
和 ClearQuotations
这三个方法允许您指定自己的引用规则,除了或代替默认规则。AddQuotation
接受一个 QuotationInfo
类型的单个参数。此对象包含一个 QuotationMark
属性,类型为 char
,表示要使用的引号字符。空字符('\0'
)的特殊值表示指定的转义序列适用于任何未加引号的值。QuotationInfo
对象还包含指定转义序列(单字符长度)的方法。这允许您指定一个可以转义的字符,以及它应该被替换的字符。
列表 6.1:设置引用的示例
// Create a quotation for the single quote character (')
QuotationInfo qinfo = new QuotationInfo('\'');
// Let us escape the single quote
qinfo.AddEscapeCode('\'', '\'');
// Also let us escape the double quote
qinfo.AddEscapeCode('\"', '\"');
// And allow us to escape the escape character
qinfo.AddEscapeCode('\\', '\\');
// And finally the newline character (represented by 'n')
qinfo.AddEscapeCode('n', '\n');
// Now let us add this to the parser
parser.AddQuotation(qinfo);
上面的示例通过将单引号引入为解析器的有效引用方法,并添加换行符转义序列,说明了这一点。
6.3 指定转义字符
转义字符本身也可以从其默认值(反斜杠(\
)字符)进行更改。实际上可以指定多个允许的转义字符。然而,在我所知的几乎所有地方,反斜杠都是标准,但我听说有些 OS 使用“^
”字符进行转义。选项就在那里,如果你需要的话,通过 CommandLineParser
的 SetEscapeCharacters()
方法完成,该方法接受一个包含有效转义字符的枚举。
7 实现细节 (简述)
Plossum
命令行库显然大量使用反射来通过使用特性简化其使用。在 CommandLineParser
构造函数中进行了大量的错误检查,以确保特性的放置及其参数的合理性。解析器是手工编写的,实际的解析并不复杂,尽管错误检查和所有选项的处理使得 Parse
方法中的代码量相当大。解析器会反复调用词法分析器的 getNextToken()
,该词法分析器也是手工编写的。词法分析器的任务是解析命令行字符,将其转换为所谓的 Token
。存在以下令牌并由词法分析器返回:
OptionNameToken
表示选项的名称,并包含有关如何引入该选项的信息。AssignmentToken
表示一个可用的赋值字符。ValueToken
表示分配给选项的值,或者剩余的参数之一。引号被剥离,转义字符被词法分析器转义。FileToken
表示命令行中指定的列表文件或选项文件。
解析器实际上有一个词法分析器堆栈,因此当读取 FileToken
时,它可以创建一个使用该文件作为输入的新词法分析器,将其推送到堆栈上,使其成为“当前词法分析器”,并继续从堆栈顶部的词法分析器中检索令牌。当一个词法分析器没有更多令牌时,它只需将词法分析器从堆栈中弹出,并继续从堆栈顶部的新的词法分析器中读取,直到堆栈变空,此时解析完成。
字符串格式化通过 Plossum.StringFormatter
类完成,该类包含一组 static
方法,用于单词换行并将字符串格式化为列。API 文档对此有更多详细信息。
8 结语
我认为这个库提供了一套非常可定制且易于使用的类,用于指定和解析命令行参数。这毕竟是开发这个库的目标,我觉得我已经成功了。
截至编写本教程时,该库尚未经过广泛测试,很可能包含一些错误。请通过报告发现的任何错误或提交补丁来帮助我(和您自己)。
Plossum
库实际上包含的不仅仅是命令行解析器,尽管此功能已从本网页上找到的版本中剥离。Plossum
库是一个托管在 SourceForge.net 上的开源项目,可以在这里找到。请使用该页面上的跟踪器提交任何错误或功能请求。此库的最新版本始终可以在那里找到。
如果您想为该库做出贡献,例如通过修复错误、添加功能或(非常感谢)翻译字符串,请在 SourceForge 的论坛上发一条便条。如果您有其他问题,也请在论坛上提问。
希望您发现这个库很有用,并且不要犹豫在论坛上提问或提交错误报告。
参考文献
[1] Wikipedia 上的“命令行参数”,2007 年 7 月[2] 自由软件基金会。使用 getopt 解析程序选项,2001 年 7 月
[3] Ray Hayes。C# 中的自动命令行解析,2003 年 5 月
[4] Peter Sestoft Niels Kokholm。C5 C# 和 CLI 的通用集合库,2007 年 5 月
[5] Klaus Prückl。GNU getopt .NET,2004 年 5 月
修订
- 版本 2 (2007 年 8 月 15 日, Peter Palotas):更改了许可
- 版本 1 (2007 年 8 月 4 日, Peter Palotas):原始文档