在其发展的早期,UNIX®的命令行环境(当时是其唯一的用户界面)被数十种小型文本处理工具所控制。 这些工具很小,通常做得很好。 这些工具通过较长的命令管道链接在一起,一个程序将其输出传递给下一个作为输入,并由各种命令行选项和参数控制。
这是UNIX的一个方面,它使它成为处理基于文本的数据的极为强大的环境,这是其在公司环境中的首次使用。 将一些文本转储到命令管道的一端,并从另一端检索处理后的输出。
命令行选项和参数控制UNIX程序并告诉它们如何运行。 作为开发人员,您有责任从传递给程序的main()
函数的命令行中发现用户的意图。 本文向您展示如何使用标准的getopt()
和getopt_long()
函数来简化命令行处理,并且它介绍了一种用于跟踪命令行选项的技术。
在你开始前
本文附带的示例代码(请参阅可下载资源 )是使用C Development Tooling(CDT)在Eclipse 3.1中编写的。 getopt_demo和getopt_long_demo项目是Managed Make项目,它们是使用CDT的程序生成规则构建的。 您不会在项目中找到Makefile,但它是如此琐碎,如果您需要在Eclipse之外编译代码,那么生成一个Makefile就不会有麻烦。
如果您还没有尝试使用Eclipse(请参阅参考资料 ),那么您应该真正尝试一下-它是一个出色的集成开发环境(IDE),每个发行版都会变得更好。 这是来自顽固的EMACS和基于Makefile的开发人员。
指令行
在开发新程序时,面临的首要障碍之一就是如何控制命令行参数的行为。 它们从命令行作为整数计数(传统上称为argc )和指向字符串的指针数组(传统上称为argv )传递到程序的main()
函数。 可以用两种基本相同的不同方式声明标准的main()
函数,如清单1所示。
清单1. main()
函数的双重寿命
int main( int argc, char *argv[] );
int main( int argc, char **argv );
第一个带有指向char
的指针的数组,这些天似乎更时尚,并且比第二个带有指向char
指针的版本的混乱要少。 由于某种原因,我倾向于更频繁地使用第二种形式,这可能代表了我在高中时对C指针学习曲线的来之不易的胜利。 出于所有意图和目的,它们是相同的,因此请使用对您最有吸引力的一种。
当C运行时库的程序启动代码调用main()
时,命令行已被处理。 argc
参数包含一个参数计数,而argv
包含一个指向这些参数的指针数组。 对于C运行时库, 参数是程序的名称,程序名称之后的任何内容都应由空格分隔。
例如,如果您运行一个名为foo的程序,其参数为-v bar www.ibm.com
,则您的argc将被设置为4,而argv
将被设置为如清单2所示。
清单2. argv的内容
argv[0] - foo
argv[1] - -v
argv[2] - bar
argv[3] - www.ibm.com
一个程序只有一组命令行参数,因此我将这些信息存储在一个跟踪选项和设置的全局结构中。 对于程序进行全局跟踪的任何有意义的事情都可以采用这种结构,而我正在使用一种结构来帮助减少全局变量的数量。 正如我在网络服务设计文章(请参阅参考资料 )中提到的那样,全局变量不利于线程编程,因此,谨慎使用它们是一个好主意。
该示例代码将显示一个虚构的doc2html程序的命令行处理。 doc2html程序将某种文档转换为HTML,由用户指定的命令行选项控制。 它支持以下选项:
-
-I
不创建关键字索引。 -
-l lang
转换为使用语言代码lang
指定的语言。 -
-o outfile.html
将翻译后的文档写到outfile.html,而不是打印到标准输出。 -
-v
翻译时冗长; 可以多次指定以提高诊断水平。 - 其他文件名将用作输入文档。
您还将支持-h
和-?
打印帮助消息,以提醒用户有关这些选项的信息。
简单的命令行处理: getopt()
清单3显示了存在于unistd.h系统头文件中的getopt()
函数:
清单3. getopt()
原型
int getopt( int argc, char *const argv[], const char *optstring );
给定许多命令行参数( argc
),指向这些参数的指针数组( argv
)和选项字符串( optstring
), getopt()
返回第一个选项,并设置一些全局变量。 当再次使用相同的参数调用它时,它将返回下一个选项,并设置全局变量。 如果找不到更多可识别的选项,则返回-1
并完成操作。
由getopt()
设置的全局变量包括:
-
optarg
指向当前选项参数的指针(如果有)。 -
optind
再次调用getopt()
时要处理的下一个argv指针的索引。 -
optopt
这是最后一个已知的选项。
选项字符串( optstring
)是每个选项一个字符。 带有参数的选项(例如本例中的-l
和-o
选项)后跟:
字符。 该optstring
使用的optstring
是Il:o:vh?
(请记住,您还希望支持用于打印程序使用情况消息的最后两个选项)。
您反复调用getopt()
,直到返回-1
为止; 其余任何命令行参数通常被认为是文件名或其他适合该程序的名称。
getopt()
的作用
让我们来看一下getopt_demo项目的代码; 我将其拆分为便于讨论的部分,但是您可以在可下载的源代码(请参阅可下载的资源 )中看到它的全部内容。
在清单4中 ,您可以看到该演示程序使用的系统头文件。 对于标准I / O函数原型,带有stdio.h
标准票价,对于EXIT_SUCCESS
和EXIT_FAILURE
, stdlib.h
;对于getopt()
EXIT_FAILURE
, unistd.h
。
清单4.系统头
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
清单5显示了我创建的globalArgs
结构,用于以明智的方式存储命令行选项。 由于它是一个全局变量,因此程序中任何地方的代码都可以访问这些变量,以查看是否创建关键字索引,生成哪种语言等等。 对于main()
函数外部的代码,将此结构视为一个恒定的只读存储区是一个好主意,因为程序的任何部分都可能取决于其内容。
每个命令行选项有一个变量,还有一些额外的变量来存储输出文件名,指向输入文件列表的指针以及输入文件的数量。
清单5.全局参数存储和选项字符串
struct globalArgs_t {
int noIndex; /* -I option */
char *langCode; /* -l option */
const char *outFileName; /* -o option */
FILE *outFile;
int verbosity; /* -v option */
char **inputFiles; /* input files */
int numInputFiles; /* # of input files */
} globalArgs;
static const char *optString = "Il:o:vh?";
选项字符串optString
告诉getopt()
您可以处理哪些选项,以及哪些选项需要参数。 如果在处理过程中遇到其他选项,则getopt()
显示一条错误消息,并且在显示使用情况消息后程序将退出。
清单6包含一些较小的存根,用于下面的main()
引用的用法消息函数和文档转换函数。 随意使它们做点有用的事!
清单6.存根
void display_usage( void )
{
puts( "doc2html - convert documents to HTML" );
/* ... */
exit( EXIT_FAILURE );
}
void convert_document( void )
{
/* ... */
}
最后,使用清单7 ,您已经进入了main()
函数。 像优秀的开发人员一样,您需要在开始处理命令行参数之前初始化globalArgs
结构。 在您的程序中,您可以使用它在一个地方为您的选项设置合理的默认值,如果更合理的默认值被发现,这将使以后更容易进行调整。
清单7.初始化
int main( int argc, char *argv[] )
{
int opt = 0;
/* Initialize globalArgs before we get to work. */
globalArgs.noIndex = 0; /* false */
globalArgs.langCode = NULL;
globalArgs.outFileName = NULL;
globalArgs.outFile = NULL;
globalArgs.verbosity = 0;
globalArgs.inputFiles = NULL;
globalArgs.numInputFiles = 0;
清单8中的while
循环和switch
语句是此程序的命令行处理的内容。 每当getopt()
发现一个选项时, switch
语句就会确定找到哪个选项,并在globalArgs
结构中记下该globalArgs
。 当getopt()
最后返回-1
,您就完成了处理选项的操作,其余参数为您的输入文件。
清单8.使用getopt()
处理argc / argv
opt = getopt( argc, argv, optString );
while( opt != -1 ) {
switch( opt ) {
case 'I':
globalArgs.noIndex = 1; /* true */
break;
case 'l':
globalArgs.langCode = optarg;
break;
case 'o':
globalArgs.outFileName = optarg;
break;
case 'v':
globalArgs.verbosity++;
break;
case 'h': /* fall-through is intentional */
case '?':
display_usage();
break;
default:
/* You won't actually get here. */
break;
}
opt = getopt( argc, argv, optString );
}
globalArgs.inputFiles = argv + optind;
globalArgs.numInputFiles = argc - optind;
既然您已经完成了收集参数和选项的工作,则可以执行构建程序所基于的所有操作(在本例中为转换文档),然后退出( 清单9 )。
清单9.开始工作
convert_document();
return EXIT_SUCCESS;
}
在那里,完成。 完善。 您现在可以停止阅读。 除非您希望使程序达到90年代后期的标准并支持在GNU应用程序中流行的长选项。
复杂的命令行处理: getopt_long()
在1990年代的某个时候(如果有内存可用),UNIX应用程序开始支持长选项,一对破折号(而不是用于普通短选项的单个破折号),描述性选项名称,以及可能以相等的方式连接到该选项的参数标志。
幸运的是,您可以使用getopt_long()
在程序中添加对长选项的支持。 您可能已经猜到了, getopt_long()
是getopt()
的版本,除了短选项之外,还支持长选项。
getopt_long()
函数采用其他参数,其中一个是指向struct option
对象数组的指针。 如清单10所示 ,该结构很简单。
清单10. getopt_long()
选项
struct option {
char *name;
int has_arg;
int *flag;
int val;
};
name
成员是长选项名称的指针,没有双破折号。 has_arg
成员设置为no_argument
, optional_argument
或required_argument
(均在getopt.h
定义)之一,以指示此选项是否具有参数。 如果标志成员未设置为NULL,则在处理过程中遇到此选项时,它指向的int
将用val
成员中的值填充。 如果标志成员为NULL
,则遇到此选项时, getopt_long()
将返回val
的值; 通过将val
设置为选项的short
参数,可以使用getopt_long()
而不添加任何其他代码-现有的getopt()
处理while loop
和switch
自动处理此选项。
由于选项现在可以具有可选参数,因此这已经更加灵活。 更重要的是,只需很少的工作即可轻松放入现有代码。
让我们看看如何使用getopt_long()
更改示例程序(可在可下载资源中找到getopt_long_demo项目)。
使用getopt_long()
由于getopt_long_demo与您已经看过的getopt_demo代码几乎相同,因此我将带您逐步了解已更改的位。 因为现在有了更大的灵活性,所以您还将添加对--randomize
选项的支持,而没有相应的short选项。
getopt_long()
函数位于getopt.h
标头中,而不是unistd.h
,因此您需要包括它(请参见清单11 )。 我还包含了string.h
,因为稍后您将使用strcmp()
来帮助您确定要处理的长参数。
清单11.其他头
#include <getopt.h>
#include <string.h>
您已经为--randomize
选项在globalArgs
添加了一个标志(请参见清单12 ),并创建了longOpts
数组来保存有关该程序支持的long选项的信息。 除了--randomize
,所有参数都与现有的短选项相对应(例如--no-index
与-I
相同)。 通过将它们的短选项等效项包括在选项结构中的最后一个条目中,您可以处理等效的长选项,而无需在程序中添加任何额外的代码。
清单12.扩展的参数
struct globalArgs_t {
int noIndex; /* -I option */
char *langCode; /* -l option */
const char *outFileName; /* -o option */
FILE *outFile;
int verbosity; /* -v option */
char **inputFiles; /* input files */
int numInputFiles; /* # of input files */
int randomized; /* --randomize option */
} globalArgs;
static const char *optString = "Il:o:vh?";
static const struct option longOpts[] = {
{ "no-index", no_argument, NULL, 'I' },
{ "language", required_argument, NULL, 'l' },
{ "output", required_argument, NULL, 'o' },
{ "verbose", no_argument, NULL, 'v' },
{ "randomize", no_argument, NULL, 0 },
{ "help", no_argument, NULL, 'h' },
{ NULL, no_argument, NULL, 0 }
};
清单13将getop()
调用更改为getopt_long()
,除了getopt()
的参数外,该方法还使用longOpts
数组和一个int
指针( longIndex
)。 当getopt_long()
返回0
时, longIndex
指向的整数将设置为当前找到的long选项的索引。
清单13.新增和改进的选项处理
opt = getopt_long( argc, argv, optString, longOpts, &longIndex );
while( opt != -1 ) {
switch( opt ) {
case 'I':
globalArgs.noIndex = 1; /* true */
break;
case 'l':
globalArgs.langCode = optarg;
break;
case 'o':
globalArgs.outFileName = optarg;
break;
case 'v':
globalArgs.verbosity++;
break;
case 'h': /* fall-through is intentional */
case '?':
display_usage();
break;
case 0: /* long option without a short arg */
if( strcmp( "randomize", longOpts[longIndex].name ) == 0 ) {
globalArgs.randomized = 1;
}
break;
default:
/* You won't actually get here. */
break;
}
opt = getopt_long( argc, argv, optString, longOpts, amp;longIndex );
}
我还添加了一个案例0
,您可以在其中处理任何不映射到现有短选项的长选项。 在这种情况下,您只有一个长选项,但是代码仍然使用strcmp()
来确保它是您所期望的。
这里的所有都是它的; 该程序现在支持更多冗长的选项(并且对用户更友好)。
摘要
UNIX用户一直依靠命令行参数来修改程序的行为,尤其是设计为用作UNIX Shell环境的小工具集合的一部分的实用程序。 程序需要能够快速处理选项和参数,而又不浪费开发人员的大量时间。 毕竟,很少有程序被设计为仅处理命令行参数,而开发人员宁愿从事该程序的实际工作。
getopt()
函数是一个标准的库调用,使您可以循环使用程序的命令行参数并使用简单的while / switch习惯轻松地检测选项(有或没有附加参数)。 它的表亲getopt_long()
允许您处理更具描述性的long选项,而几乎无需进行其他工作,这使开发人员感到非常高兴。
既然您已经了解了如何轻松处理命令行选项,则可以通过添加对长选项的支持以及添加可能因为您不想执行而推迟的任何其他选项来专注于改进程序的命令行向程序添加其他命令行选项处理。
不要忘记在某个地方记录所有选项和参数,并提供某种内置的帮助功能来帮助提醒健忘的用户。
翻译自: https://www.ibm.com/developerworks/aix/library/au-unix-getopt.html