在Unix-like操作系统中,C语言的编程人员经常使用getopt函数来解析命令行参数。然而,在Windows和Mac OS等操作系统中,getopt的使用并不普遍。为了解决这个问题,CpGetOpt项目应运而生。CpGetOpt是一个用C#编写的.NET项目,它提供了一个(目前还不完全)的getopt实现,并且是POSIX兼容的。它甚至可以可选地几乎完全模拟glibc版本的getopt。虽然目前还不支持长选项,但很快就会支持。
Getopt已经存在很长时间了,它帮助程序员处理可能变得复杂的任务:解析程序参数以及它们自己可以携带的参数。Getopt是大多数C运行时库的一部分,并且在许多编程语言中都有实现。通常,getopt只支持Unix风格的命令选项,但实现Windows风格的选项支持并不困难(见本节的最后一段)。
什么是选项?选项允许程序员以更有结构的方式处理可选的程序参数。本质上,选项是特殊类型的命令行参数,意味着给程序本身的标志或变量赋予意义,正如它们的名字所暗示的,是可选的。使用getopt比简单地检查程序参数的数量并据此解释它们要简单得多。
选项是如何通过命令行传递给应用程序的?很简单;一个选项就是用一个破折号("-")前缀,然后是一个识别被指定选项的字符,然后是一个给定选项的参数(如果允许任何参数的话)。此外,可以通过两种方式向选项提供参数。第一种方式:选项后面跟着一个空格,然后是参数;第二种方式:选项后面跟着参数,没有空格(当使用这种形式时,选项字符必须紧跟在破折号后面,后面的字符不能是选项本身;"-afoo"如果'f'也是一个有效的选项,就不会按预期工作)。
例如:
app.exe -a foo
app.exe -afoo
此外,假设使用第一种形式来指定传递给程序的选项,可以将多个选项合并到一个字符串中。
app.exe -abc
app.exe -a -b -c
如果选项'b'和'c'需要一个参数,选项字符串可以保持不变,参数只需要按照它们各自的顺序跟随在选项后面。
app.exe -abc foo bar
app.exe -a -b foo -c bar
什么是长选项?长选项就像常规选项一样,唯一的区别是它们可以超过一个字符的长度(例如,--verb而不是-v)。并且,在选项字符串中包含选项参数时,可以通过一个等号("=")将两者分开,没有空格。
例如,对于一个应用程序,选项'i'和'include'有相同的含义,
app.exe -iarg
app.exe -i arg
app.exe --include=arg
app.exe --include arg
不幸的是,CpGetOpt目前还不支持长选项,但很快就会支持。
"getopt"是大多数libc库的标准组件,也是POSIX规范的一部分;但正如前面提到的,它不包含在Microsoft的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方法是必须用来连续解析命令行参数的,它接受三个参数,第三个是可选的。
C# 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,使用返回的字符来处理给定的选项。
C# // // 通常,getopt在循环中被调用。当getopt返回-1,表示 // 没有更多的选项存在时,循环终止。 // // 使用switch语句来处理getopt的返回值。 // 在典型使用中,每个案例只是设置一个变量,该变量稍后在程序中使用。 // // 使用第二个循环来处理剩余的非选项参数。 // // 确保在项目中添加CpGetOpt.dll作为程序集引用 // 然后只需添加一个"using CodePoints;"语句。 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) 非选项参数:arg1
% testopt.exe -a arg1 aflag = 1, bflag = 0, cvalue = (null) 非选项参数:arg1
% testopt.exe -c foo arg1 aflag = 0, bflag = 0, cvalue = foo 非选项参数:arg1
% testopt.exe -a -- -b aflag = 1, bflag = 0, cvalue = (null) 非选项参数:-b