`

使用 getopt() 进行命令行处理

阅读更多

使用 getopt() 进行命令行处理

2006 年 5 月 25 日

所有 UNIX® 程序甚至那些具有图形用户界面(graphical user interface,GUI)的程序,都能接受和处理命令行选项。对于某些程序,这是与其他程序或用户进行交互的主要手段。具有可靠的复杂命令行参数处理 机制,会使得您的应用程序更好、更有用。不过很多开发人员都将其宝贵的时间花在了编写自己的命令行解析器,却不使用 getopt() ,而后者是一个专门设计来减轻命令行处理负担的库函数。请阅读本文,以了解如何让 getopt() 在全局结构中记录命令参数,以便随后随时在整个程序中使用。
<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->

引言

在 早期的 UNIX® 中,其命令行环境(当时的唯一用户界面)包含着数十种小的文本处理工具。这些工具非常小,通常可很好地完成一项工作。这些工具通过较长的命令管道链接在一 起,前面的程序将其输出传递给下一个程序以作为输入,整个过程由各种命令行选项和参数加以控制。

正是 UNIX 的这方面的特征使其成为了极为强大的处理基于本文的数据的环境,而这也是其在公司环境中的最初用途之一。在命令管道的一端输入一些文本,然后在另一端检索经过处理的输出。

命令行选项和参数控制 UNIX 程序,告知它们如何动作。作为开发人员,您要负责从传递给您程序的 main() 函数的命令行发现用户的意图。本文将演示如何使用标准 getopt()getopt_long() 函数来简化命令行处理工作,并讨论了一项用于跟踪命令行选项的技术。

开始之前

本文包含的示例代码(请参见下载 )是使用 C 开发工具(C Development Tooling,CDT)在 Eclipse 3.1 中编写的;getopt_demogetopt_long_demo 项目是 Managed Make 项目,均使用 CDT 的程序生成规则构建。在项目中没有包含 Makefile,如果需要在 Eclipse 外编译代码,可以自己方便地生成一个。

如果尚未尝试过 Eclipse(请参阅参考资料 ),真的应该尝试一下——这是一个优秀的集成开发环境(integrated development environment,IDE),其每个新版本都有较大的提升。这是来自“强硬派” EMACS 和 Makefile 开发人员的作品。

命令行

在编写新程序时,首先遇到的障碍之一就是如何处理控制其行为的命令行参数。这包括从命令行传递给您程序的 main() 函数的一个整数计数(通常名为 argc )和一个指向字符串的指针数组(通常名为 argv ).可以采用两种实质一样的方式声明标注 main() 函数,如清单 1 中所示。


清单 1. 声明 main() 函数的两种方式

                
int main( int argc, char *argv[] );
int main( int argc, char **argv );

 

第一种方式使用的是指向 char 指针数组,现在似乎很流行这种方式,比第二种方式(其指针指向多个指向 char 的指针)略微清楚一些。由于某些原因,我使用第二种方式的时间更多一些,这可能源于我在高中时艰难学习 C 指针的经历。对于所有的用途和目的,这两种方法都是一样的,因此可以使用其中您自己最喜欢的方式。

当 C 运行时库的程序启动代码调用您的 main() 时,已经对命令行进行了处理。argc 参数包含参数的计数值,而 argv 包含指向这些参数的指针数组。对于 C 运行时库,arguments 是程序的名称,程序名后的任何内容都应该使用空格加以分隔。

例如,如果使用参数 -v bar www.ibm.com 运行一个名为 foo 程序,您的 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()

getopt() 函数位于 unistd.h 系统头文件中,其原型如清单 3 中所示:


清单 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 选项)后面跟有一个 : 字符。示例所使用的 optstringIl:o:vh? (前面提到,还要支持最后两个用于打印程序的使用方法消息的选项)。

可以重复调用 getopt() ,直到其返回 -1 为止;任何剩下的命令行参数通常视为文件名或程序相应的其他内容。

getopt() 的使用

让我们对 getopt_demo 项目的代码进行一下深入分析;为了方便起见,我在此处将此代码拆分为多个部分,但您可以在可下载源代码部分获得完整的代码(请参见下载 )。

清单 4 中,可以看到系统演示程序所使用的系统头文件;标准 stdio.h 提供标准 I/O 函数原型,stdlib.h 提供 EXIT_SUCCESSEXIT_FAILUREunistd.h 提供 getopt()


清单 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 结构中看到具体情况。当 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()

在 20 世纪 90 年代(如果没有记错的话),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_argumentoptional_argument , 或 required_argument (均在 getopt.h 中定义)之一,以指示选项是否具有参数。如果 flag 成员未设置为 NULL,在处理期间遇到此选项时,会使用 val 成员的值填充它所指向的 int 值。如果 flag 成员为 NULL ,在 getopt_long() 遇到此选项时,将返回 val 中的值;通过将 val 设置为选项的 short 参数,可以在不添加任何其他代码的情况下使用 getopt_long() ——处理 while loopswitch 的现有 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. 其他头文件

                
#include <getopt.h>
#include <string.h>

 

您已经为 --randomize 选项在 globalArgs 中添加了一个标志(请参见清单 12 ),并创建了 longOpts 数组来存储关于此程序支持的长选项的信息。除了 --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 }
};

 

清单 13getop() 调用更改为了 getopt_long() ,除了 getopt() 的参数外,它还接受 longOpts 数组和 int 指针 (longIndex )。当 getopt_long() 返回 0 时,longIndex 所指向的整数将设置为当前找到的长选项的索引。


清单 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 的 case,以便处理任何不与现有短选项匹配的长选项。在此例中,只有一个长选项,但代码仍然使用 strcmp() 来确保它是预期的那个选项。

这样就全部搞定了;程序现在支持更为详细(对临时用户更加友好)的长选项。

总结

UNIX 用户始终依赖于命令行参数来修改程序的行为,特别是那些设计作为小工具集合 (UNIX 外壳环境)的一部分使用的实用工具更是如此。程序需要能够快速处理各个选项和参数,且要求不会浪费开发人员的太多时间。毕竟,几乎没有程序设计为仅处理命令行参数,开发人员更应该将精力放在程序所实际进行的工作上。

getopt() 函数是一个标准库调用,可允许您使用直接的 while/switch 语句方便地逐个处理命令行参数和检测选项(带或不带附加的参数)。与其类似的 getopt_long() 允许在几乎不进行额外工作的情况下处理更具描述性的长选项,这非常受开发人员的欢迎。

既然已经知道了如何方便地处理命令行选项,现在就可以集中精力改进您的程序的命令行,可以添加长选项支持,或添加之前由于不想向程序添加额外的命令行选项处理而搁置的任何其他选项。

不要忘记在某处记录您所有的选项和参数,并提供某种类型的内置帮助函数来为健忘的用户提供帮助。

 

分享到:
评论

相关推荐

    C#getopt(命令行处理函数)

    C#中的getopt封装,方便处理cmd命令。添加引用后,使用范例如下: using CodePoints; using System; ... public static void Main ( string [] args ) { int c = 0, aflag = 0, bflag = 0; string cvalue = "...

    GetOpt一个命令行参数解析器

    GetOpt是PHP中用于解析命令行参数的一个工具,它使得在命令行脚本中处理输入参数变得更加方便和规范。在PHP开发中,特别是在构建命令行界面(CLI)应用程序时,GetOpt扮演着至关重要的角色。它能够帮助开发者从传递...

    getopt.h getopt.c getopt getopt_long 字符串 命令行

    总之,`getopt.h` 和 `getopt.c` 是处理C语言程序命令行参数的强大工具,通过使用它们,开发者可以创建具有灵活选项的命令行界面,提高程序的可配置性和用户体验。这两个文件的使用涉及到了命令行参数解析、字符串...

    getopt-php, 用于 命令行 参数处理的PHP库.zip

    getopt-php, 用于 命令行 参数处理的PHP库 GetOpt.PHP GetOpt.PHP 是命令行参数处理的库。 它支持PHP版本 5.4和更高版本。特性同时支持短( ( -v ) ) 和长( 例如。 --version ) 选项选项别名。IE 。选项既

    命令行解析函数getopt用法详解

    命令行解析函数getopt用法详解 getopt函数是命令行参数解析函数,用于解析命令行参数。...`getopt`函数是一个非常有用的命令行参数解析函数,可以帮助程序正确地处理命令行参数,使程序更加灵活和可靠。

    Linux操作系统的程序命令行处理分析

    在Linux操作系统中,程序命令行处理...总的来说,Linux命令行处理是高效管理系统的基石,理解并掌握`getopt_long`等工具的使用,可以极大地提高开发和维护效率。熟练运用这些技巧,将有助于更好地利用Linux的强大功能。

    getopt多参数解析函数具体分析命令行参数解析

    `getopt()`函数是C语言中用于解析命令行参数的标准函数,主要应用于Unix/Linux系统,也可以在其他支持C语言的环境中使用。它简化了对带有选项和参数的命令行输入的处理,使得程序能够优雅地解析和理解用户提供的...

    哈密顿回路.xcodeproj_CCF命令行处理_

    命令行处理是指程序接收来自操作系统命令行界面的参数并进行解析的过程。在Unix和类Unix系统(如Linux和macOS)中,命令行参数通常以空格分隔,并且第一个参数是程序的名称。在示例中,“-a -l -b -w”是传递给...

    cpp-optparse是一个开源可移植的可重入的和可嵌入的类getopt命令行参数解析器

    在POSIX系统中,getopt函数是一个标准的命令行选项解析器,它能够处理短格式的命令行选项,如`-f`或`-v`。cpp-optparse不仅支持这种基本的POSIX风格,还增加了对GNU风格的长参数的支持,比如`--file`或`--verbose`。...

    getopt.h和cpp

    将这个 `getopt.cpp` 文件添加到Windows项目中,可以使得习惯于使用 `getopt` 的开发者在Windows环境下也能轻松地处理命令行参数。这对于跨平台开发非常有用,因为它允许你在不同的操作系统上保持代码的一致性。 总...

    getopt源码下载及getopt_long源码下载

    通过阅读和分析提供的代码,可以更深入地了解`getopt`和`getopt_long`的工作方式,同时也可以学习如何在自己的程序中使用它们来优雅地处理命令行参数。这不仅有助于提高代码的可读性和可维护性,还能使程序更加符合...

    getopt源码,32,64库

    2. 使用`getopt()`函数来解析命令行参数,它会返回当前处理的选项,并更新全局变量`optind`和`optarg`。 3. 根据`getopt()`返回的选项值来处理相应的功能。 4. 编译程序时链接`getopt`库,如果是静态库,使用`-...

    getopt.h getopt.c getopt_long 文件和函数说明

    `getopt.h` 和 `getopt.c` 是在C语言编程中处理命令行选项解析的两个关键文件。`getopt` 函数是C标准库的一部分,通常用于解析命令行参数,而`getopt_long` 是一个扩展功能,提供更方便的方式来处理长选项。 `...

    Python getopt模块处理命令行选项实例

    getopt模块用于抽出命令行选项和参数,也就是sys.argv命令行选项使得程序的参数更加灵活。支持短选项模式和长选项模式例如 python scriptname.py -f ‘hello’ –directory-prefix=/home -t –format ‘a’ ‘b’...

    getopt:简单的命令行选项处理程序(C ++ 11)

    getopt 简单的命令行选项处理程序(C ++ 11)。特征 方便,每个选项一行。 微型,跨平台,自包含,仅标头。 ZLIB / libPNG许可。展示柜#&gt; cat sample.cc# include &lt; iostream&gt;# include &lt; string&gt;# include " getopt...

    在linux下的getopt源码

    `getopt`函数的基本使用方式是,它会从程序的`argv`参数中按顺序解析命令行选项,并返回当前处理的选项。选项通常以破折号(-)开头,后面跟随一个字母或数字,如`-h`表示帮助,`-v`表示详细模式等。getopt函数可以...

    RTMP可用的getOpt.h和getOpt.c

    总的来说,`getOpt.h`和`getOpt.c`是处理命令行选项的利器,它们简化了命令行参数的解析工作,使得RTMP库的使用更加灵活和方便。在进行RTMP相关开发时,理解和正确使用这两个文件是至关重要的。

    getopt在win32的实现(含源代码)

    `getopt`是一个在Linux系统中广泛使用的命令行选项解析函数,它可以帮助程序开发者处理命令行参数,使得程序能够接收并解析用户在命令行输入的各种选项。然而,`getopt`在Windows环境下并不直接可用,因为Windows的...

    getopt源文件

    `getopt`是Unix/Linux系统中广泛使用的命令行选项解析函数,它允许程序在命令行中接收带有短格式(单个字符)或长格式(单词)的参数。这个源文件通常包含C语言实现的`getopt`函数及其相关辅助功能。在深入探讨`...

    getopt windows实现

    例如,如果你的程序可以接受一个文件名作为参数,即使用户使用了`--file`这种看起来像是选项的形式,`getopt_long_only`也会正确处理。 在Windows环境下实现这些函数,通常有以下几种方法: 1. **移植源代码**:你...

Global site tag (gtag.js) - Google Analytics