适用于 .NET 的 GetOpt
一个适用于 .NET 的 GetOpt 实现。
引言
"getopt" 在类 UNIX 操作系统上的 C 编程中是一个熟悉的函数,但在其之外(例如,Windows、Mac OS)几乎不存在。getopt 的目的是帮助程序员解析通过命令行传递给应用程序的选项及其参数。
CpGetOpt 是这个小项目的名称,它为 .NET 提供了一个(目前是部分的)getopt 实现,该实现是用 C# 编写的。CpGetOpt 符合 POSIX 规范,并且可以选择几乎完全模拟 glibc 版本的 getopt。目前,尚不支持长选项,但很快就会支持。
有关 getopt 的更多信息,请访问 glibc 文档,网址为 www.gnu.org。
另外,请访问我的博客以查看我的更多作品。
背景
Getopt 已经存在很长时间了,它有助于处理可能变成复杂任务的事情:解析程序参数,以及它们本身可以接受的参数。Getopt 是大多数 C 运行时库的一部分,并且在许多编程语言中都有实现。通常,getopt 只支持 Unix 风格的命令选项,但支持 Windows 风格的选项并不难实现(请参阅本节的最后一段)。
什么是选项?
选项允许程序员以更结构化的方式处理可选的程序参数。实质上,选项是特殊类型的命令行参数,旨在赋予程序本身中的标志或变量意义,顾名思义,它们是可选的。选项允许程序员以更结构化的方式处理可选的程序参数。
通过检查给定的程序参数数量并据此解释它们,也可以实现相同的功能。然而,显然,这要复杂得多且耗时,而使用 getopt 则简单得多。
如何通过命令行将选项传递给应用程序?
很简单;选项只需以破折号(“-”)为前缀,然后是一个标识所指定选项的字符,然后是该选项的参数(如果允许任何参数)。此外,可以通过两种不同的方式为选项提供参数。第一种方式是:选项后跟一个空格,然后是参数;第二种方式是:选项后直接跟参数,没有空格(使用这种形式时,选项字符必须紧跟在破折号后面,并且后面的字符本身不能是选项;如果“f”也是一个有效选项,则“-afoo”不会按预期工作)。
# In this example, both commands are equivalent.
Example:
app.exe -a foo
app.exe -afoo
此外,假设您使用第一种形式来指定传递给程序的选项,您可以将多个选项组合成一个字符串。
# In this example, both commands are equivalent.
Example:
app.exe -abc
app.exe -a -b -c
如果选项“b”和“c”需要参数,选项字符串可以保持不变,参数只需按各自的顺序跟随选项。
# In this example, both commands are equivalent.
Example:
app.exe -abc foo bar
app.exe -a -b foo -c bar
什么是长选项?
长选项与常规选项完全相同,只是它们的长度可以超过一个字符(即,--verbose 而不是 -v)。并且,当在选项字符串中包含选项参数时,您需要使用等号(“=”)将两者分开,且没有空格。
# For an application where options 'i' and 'include' have the same meaning,
# the commands below are all equivalent.
Example:
app.exe -iarg
app.exe -i arg
app.exe --include=arg
app.exe --include arg
不幸的是,CpGetOpt 尚不支持长选项,但很快就会支持。
"getopt" 是大多数 libc 库的标准组件,也是 POSIX 规范的一部分;但如前所述,它在微软的 C 运行时中缺失。Windows 在调用命令时确实使用选项的概念,但有一个细微的区别:不是用前缀 "-" 或长选项用 "--" 指定选项,所有选项都简单地以单个正斜杠 ("/") 为前缀;并且在提供带有选项本身的选项参数时,不是使用 "=",而是使用冒号 (":")。
# UNIX-style
app -a -b -c foo --longopt=arg
# Windows-style
app.exe /a /b /c foo /longopt:arg
使用代码
目前,CpGetOpt 定义了两种类型:GetOptionsSettings
和 GetOpt
。
GetOptionsSettings
提供了一个枚举,可以作为一组标志传递给 GetOpt.GetOptions
函数,以控制选项的解析方式。
GlibcCorrect
- 指定GetOptions
应该与 glibc 的 getopt 函数的行为完全一致。PosixCorrect
- 指定GetOptions
应该以符合 POSIX 标准的方式运行。ThrowOnError
- 在解析过程中发生错误时抛出ApplicationException
。PrintOnError
- 发生错误时,向命令行打印错误消息。None
- 未设置任何选项;使用默认行为。
GetOpt
是提供 getopt 实现的容器类。
GetOpt
类的 GetOptions
方法是用于连续解析命令行参数的方法,它接受三个参数,其中第三个是可选的。
int GetOptions(string[] args, string options, [GetOptionsSettings settings])
args
- 一个字符串数组;由Main
方法提供给您的参数数组。options
- 一个字符串,指定有效的选项及其参数要求。此字符串必须与原始 getopt 使用的字符串格式相同。“此字符串中的选项字符后可以跟一个冒号 (:),表示它需要一个必需的参数。如果选项字符后跟两个冒号 (::),则其参数是可选的。”settings
-GetOptionsSettings
枚举值的按位 OR 组合,可以可选地将解析方法更改为其他行为。
GetOptions
返回标识选项的字符(强制转换为 int
),当当前选项导致错误时返回 '?',当所有选项都被解析后返回 -1。
默认情况下,每次成功调用 GetOptions
后,可以通过 GetOpt.Item
属性访问选项名称。但是,当指定 GetOptionsSettings.GlibcCorrect
时,此行为仅在解析导致错误的选项时才成立。
如果返回的选项带有参数,则可以通过 GetOpt.Text
属性访问该参数。
解析所有选项并由 GetOptions
返回 -1 后,访问 GetOpt.Index
属性以获取 args
数组中的索引,您可以从该索引恢复正常的参数处理。
重要提示:GetOptions
方法维护的所有状态信息都是线程静态的。这意味着在不同执行线程中对 GetOptions
的调用将相互独立。
那么它是如何工作的?
很简单;在循环中调用 GetOpt.GetOptions
直到它返回 -1,并使用返回的字符来处理传递给应用程序的选项。
//
// Normally, getopt is called in a loop. When getopt returns -1, indicating
// no more options are present, the loop terminates.
//
// A switch statement is used to dispatch on the return value from getopt.
// In typical use, each case just sets a variable that is used later in the program.
//
// A second loop is used to process the remaining non-option arguments.
//
// Make sure to add CpGetOpt.dll as an assembly reference in your project
// and then just add a "using CodePoints;" statement.
using CodePoints;
using System;
...
public static void Main ( string [] args ) {
int c = 0, aflag = 0, bflag = 0;
string cvalue = "(null)";
while ( ( c = GetOpt.GetOptions(args, "abc:") ) != ( -1 ) ) {
switch ( ( char ) c ) {
case 'a':
aflag = 1;
break;
case 'b':
bflag = 1;
break;
case 'c':
cvalue = GetOpt.Text;
break;
case '?':
Console.WriteLine("Error in parsing option '{0}'", GetOpt.Item);
break;
default:
return;
}
}
Console.WriteLine("aflag = {0}, bflag = {1}, cvalue = {2}", aflag, bflag, cvalue);
for ( int n = GetOpt.Index ; n < args.Length ; n++ )
Console.WriteLine("Non-option argument: {0}", args [n]);
}
...
以下是一些示例,展示了此程序在不同参数组合下的输出
% testopt.exe
aflag = 0, bflag = 0, cvalue = (null)
% testopt.exe -a -b
aflag = 1, bflag = 1, cvalue = (null)
% testopt.exe -ab
aflag = 1, bflag = 1, cvalue = (null)
% testopt.exe -c foo
aflag = 0, bflag = 0, cvalue = foo
% testopt.exe -cfoo
aflag = 0, bflag = 0, cvalue = foo
% testopt.exe arg1
aflag = 0, bflag = 0, cvalue = (null)
Non-option argument: arg1
% testopt.exe -a arg1
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument: arg1
% testopt.exe -c foo arg1
aflag = 0, bflag = 0, cvalue = foo
Non-option argument: arg1
% testopt.exe -a -- -b
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument: -b
% testopt.exe -a -
aflag = 1, bflag = 0, cvalue = (null)
Non-option argument: -
关注点
如果您想了解我是如何实现 getopt 的,请查看源代码。我想说的是:正确且功能性地实现 getopt 并不像乍看起来那么容易。无论如何,我希望有人能够利用并发现 CpGetOpt 的一些用途。