使用 getopt() 进行命令行处理
2017-11-08 09:24
435 查看
引言
在早期的 UNIX® 中,其命令行环境(当时的唯一用户界面)包含着数十种小的文本处理工具。这些工具非常小,通常可很好地完成一项工作。这些工具通过较长的命令管道链接在一起,前面的程序将其输出传递给下一个程序以作为输入,整个过程由各种命令行选项和参数加以控制。正是 UNIX 的这方面的特征使其成为了极为强大的处理基于本文的数据的环境,而这也是其在公司环境中的最初用途之一。在命令管道的一端输入一些文本,然后在另一端检索经过处理的输出。
命令行选项和参数控制 UNIX 程序,告知它们如何动作。作为开发人员,您要负责从传递给您程序的
main()函数的命令行发现用户的意图。本文将演示如何使用标准
getopt()和
getopt_long()函数来简化命令行处理工作,并讨论了一项用于跟踪命令行选项的技术。
开始之前
本文包含的示例代码(请参见下载)是使用C 开发工具(C Development Tooling,CDT)在 Eclipse 3.1 中编写的;getopt_demo 和 getopt_long_demo 项目是 Managed
Make 项目,均使用 CDT 的程序生成规则构建。在项目中没有包含 Makefile,如果需要在 Eclipse 外编译代码,可以自己方便地生成一个。
如果尚未尝试过 Eclipse(请参阅参考资料),真的应该尝试一下——这是一个优秀的集成开发环境(integrated
development environment,IDE),其每个新版本都有较大的提升。这是来自“强硬派” EMACS 和 Makefile 开发人员的作品。
命令行
在编写新程序时,首先遇到的障碍之一就是如何处理控制其行为的命令行参数。这包括从命令行传递给您程序的 main()函数的一个整数计数(通常名为 argc)和一个指向字符串的指针数组(通常名为 argv).可以采用两种实质一样的方式声明标注
main()函数,如清单
1 中所示。
清单 1. 声明
main()函数的两种方式
char指针数组,现在似乎很流行这种方式,比第二种方式(其指针指向多个指向
char的指针)略微清楚一些。由于某些原因,我使用第二种方式的时间更多一些,这可能源于我在高中时艰难学习
C 指针的经历。对于所有的用途和目的,这两种方法都是一样的,因此可以使用其中您自己最喜欢的方式。
当 C 运行时库的程序启动代码调用您的
main()时,已经对命令行进行了处理。
argc参数包含参数的计数值,而
argv包含指向这些参数的指针数组。对于
C 运行时库,arguments 是程序的名称,程序名后的任何内容都应该使用空格加以分隔。
例如,如果使用参数
-v bar www.ibm.com运行一个名为 foo 程序,您的
argc 将设置为 4,
argv的设置情况将如清单
2 中所示。
清单 2. argv 的内容
示例代码将演示一个假想的 doc2html 程序的命令行处理。该 doc2html 程序将某种类型的文档转换为 HTML,具体由用户指定的命令行选项控制。它支持以下选项:
-I——不创建关键字索引。
-l lang——转换为使用语言代码
lang指定的语言。
-o outfile.html——将经过转换的文档写入到 outfile.html,而不是打印到标准输出。
-v——进行转换时提供详细信息;可以多次指定,以提高诊断级别。
将使用其他文件名称来作为输入文档。
您还将支持
-h和
-?,以打印帮助消息来提示各个选项的用途。
简单命令行处理: getopt()
getopt()函数位于 unistd.h 系统头文件中,其原型如清单
3 中所示:
清单 3.
getopt()原型
argc)、指向这些参数的数组 (
argv)
和选项字符串 (
optstring) 后,
getopt()将返回第一个选项,并设置一些全局变量。使用相同的参数再次调用该函数时,它将返回下一个选项,并设置相应的全局变量。如果不再有识别到的选项,将返回
-1,此任务就完成了。
getopt()所设置的全局变量包括:
optarg——指向当前选项参数(如果有)的指针。
optind——再次调用
getopt()时的下一个
argv 指针的索引。
optopt——最后一个已知选项。
对于每个选项,选项字符串 (
optstring) 中都包含一个对应的字符。具有参数的选项(如示例中的
-l和
-o选项)后面跟有一个
:字符。示例所使用的
optstring为
Il:o:vh?(前面提到,还要支持最后两个用于打印程序的使用方法消息的选项)。
可以重复调用
getopt(),直到其返回
-1为止;任何剩下的命令行参数通常视为文件名或程序相应的其他内容。
getopt()
的使用
让我们对 getopt_demo 项目的代码进行一下深入分析;为了方便起见,我在此处将此代码拆分为多个部分,但您可以在可下载源代码部分获得完整的代码(请参见下载)。在清单 4 中,可以看到系统演示程序所使用的系统头文件;标准
stdio.h提供标准
I/O 函数原型,
stdlib.h提供
EXIT_SUCCESS和
EXIT_FAILURE,
unistd.h提供
getopt()。
清单 4. 系统头文件
globalArgs结构,用于以合理的方式存储命令行选项。由于这是个全局变量,程序中任何位置的代码都可以访问这些变量,以确定是否创建关键字索引、生成何种语言等等事项。最好让
main()函数外的代码将此结构视为一个常量、只读存储区,因为程序的任何部分都可以依赖于其内容。
每个命令行选择都有一个对应的选项,而其他变量用于存储输出文件名、指向输入文件列表的指针和输入文件数量。
清单 5. 全局参数存储和选项字符串
optString告知
getopt()可以处理哪个选项以及哪个选项需要参数。如果在处期间遇到了其他选项,
getopt()将显示一个错误消息,程序将在显示了使用方法消息后退出。
下面的清单 6 包含一些从
main()引用的用法消息函数和文档转换函数的小存根。可以对这些存根进行自由更改,以用于更为有用的目的。
清单 6. 存根
main()函数中使用此结构。和优秀的开发人员一样,您需要首先初始化
globalArgs结构,然后才开始处理命令行参数。在您的程序中,可以借此设置在一定情况下合理的缺省值,以便在以后有更合适的缺省值时更方便地对其进行调整。
清单 7. 初始化
while循环和
switch语句是用于本程序的命令行处理的代码部分。只要
getopt()发现选项,
switch语句将确定找到的是哪个选项,将能在
globalArgs结构中看到具体情况。当
getopt()最终返回
-1时,就完成了选项处理过程,剩下的都是您的输入文件了。
清单 8. 使用
getopt()处理 argc/argv
9)。
清单 9. 开始工作
复杂命令行处理: getopt_long()
在 20 世纪 90 年代(如果没有记错的话),UNIX 应用程序开始支持长选项,即一对短横线(而不是普通短 选项所使用的单个短横线)、一个描述性选项名称还可以包含一个使用等号连接到选项的参数。幸运的是,可以通过使用
getopt_long()向程序添加长选项支持。您可能已经猜到了,
getopt_long()是同时支持长选项和短选项的
getopt()版本。
getopt_long()函数还接受其他参数,其中一个是指向
struct option对象数组的指针。此结构相当直接,如清单
10 中所示。
清单 10.
getopt_long()的选项
name成员是指向长选项名称(带两个短横线)的指针。
has_arg成员设置为
no_argument、
optional_argument,
或
required_argument(均在
getopt.h中定义)之一,以指示选项是否具有参数。如果
flag 成员未设置为 NULL,在处理期间遇到此选项时,会使用
val成员的值填充它所指向的
int值。如果
flag 成员为
NULL,在
getopt_long()遇到此选项时,将返回
val中的值;通过将
val设置为选项的
short参数,可以在不添加任何其他代码的情况下使用
getopt_long()——处理
while loop和
switch的现有
getopt()将自动处理此选项。
这已经变得更为灵活了,因为各个选项现在可以具有可选参数了。更重要的是,仅需要进行很少的工作,就可以方便地放入现有代码中。
让我们看看如何使用
getopt_long()来对示例程序进行更改(getopt_long_demo 项目可从下载部分获得)。
使用 getopt_long()
由于 getopt_long_demo 几乎与刚刚讨论的 getopt_demo 代码一样,因此我将仅对更改的代码进行说明。由于现在已经有了更大的灵活性,因此还将添加对 --randomize选项(没有对应的短选项)的支持。
getopt_long()函数在
getopt.h头文件(而非
unistd.h)中,因此将需要将该头文件包含进来(请参见清单
11)。我还包含了
string.h,因为将稍后使用
strcmp()来帮助确定处理的是哪个长参数。
清单 11. 其他头文件
--randomize选项在
globalArgs中添加了一个标志(请参见清单
12),并创建了
longOpts数组来存储关于此程序支持的长选项的信息。除了
--randomize外,所有的参数都与现有短选项对应(例如,
--no-index等同于
-I)。通过在选项结构中包含其短选项等效项,可以在不向程序添加任何其他代码的情况下处理等效的长选项。
清单 12. 扩展后的参数
getop()调用更改为了
getopt_long(),除了
getopt()的参数外,它还接受
longOpts数组和
int指针
(
longIndex)。当
getopt_long()返回
0时,
longIndex所指向的整数将设置为当前找到的长选项的索引。
清单 13. 新的经改进的选项处理
0的 case,以便处理任何不与现有短选项匹配的长选项。在此例中,只有一个长选项,但代码仍然使用
strcmp()来确保它是预期的那个选项。
这样就全部搞定了;程序现在支持更为详细(对临时用户更加友好)的长选项。
总结
UNIX 用户始终依赖于命令行参数来修改程序的行为,特别是那些设计作为小工具集合 (UNIX 外壳环境)的一部分使用的实用工具更是如此。程序需要能够快速处理各个选项和参数,且要求不会浪费开发人员的太多时间。毕竟,几乎没有程序设计为仅处理命令行参数,开发人员更应该将精力放在程序所实际进行的工作上。getopt()函数是一个标准库调用,可允许您使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项(带或不带附加的参数)。与其类似的
getopt_long()允许在几乎不进行额外工作的情况下处理更具描述性的长选项,这非常受开发人员的欢迎。
既然已经知道了如何方便地处理命令行选项,现在就可以集中精力改进您的程序的命令行,可以添加长选项支持,或添加之前由于不想向程序添加额外的命令行选项处理而搁置的任何其他选项。
不要忘记在某处记录您所有的选项和参数,并提供某种类型的内置帮助函数来为健忘的用户提供帮助。
相关文章推荐
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 、getopt_long()、getopt_long_only()进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt()和getopt_long 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 、getopt_long()、getopt_long_only()进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理,optind,optarg,optopt
- 使用 getopt() 进行命令行处理 ( 转 )
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理
- 使用 getopt() 进行命令行处理