将 wordexp 的输出提供给 getopt_long 会使我的 linux cli 应用程序崩溃

Posted

技术标签:

【中文标题】将 wordexp 的输出提供给 getopt_long 会使我的 linux cli 应用程序崩溃【英文标题】:feeding the output of wordexp to getopt_long crashes my linux cli application 【发布时间】:2015-08-06 17:49:58 【问题描述】:

我正在尝试创建一个小型 linux 命令行应用程序。 在调用应用程序时可以通过传递参数来运行应用程序,我使用 getopt() 对其进行解析。

可以选择以交互模式运行此应用程序,在这种情况下会显示一个小菜单,并且用户应该能够输入与他们在运行应用程序时添加的选项类似的选项。

为避免在交互模式下创建不同的解析器,我想将键盘输入解析为 argc 和 argv 并将其提供给同一个 getopt() 函数。

我创建的概念证明c 函数如下:

void menuDlg()

    char kbinput[256];
    wordexp_t we;
    char **argAr;
    int argCount = 0 ,i,er=0;

    printf("showing menu options\n");
    //...
    //...

    ///grab keyboard input
    fgets(kbinput,256,stdin);

    ///the "\n" in kbinput breaks we 
    ///and we.we_wordc return weird value, 
    ///so we need to remove it
    for (i=0; i<256; i++)
    
        if (kbinput[i]=='\n')
        
            kbinput[i]='\0';
            break;
        
    
    printf("you typed |%s|\n",kbinput);

    we.we_offs = 0;
    if ( (er=wordexp(kbinput, &we, 0)) != 0)
    
        printf("error in word expansion %d\n",er);
    
    argAr = we.we_wordv;
    argCount = we.we_wordc;
    printf("we.c=%u\n",we.we_wordc);

    main_dialog( argCount, argAr );

    wordfree(&we);

是 main_dialog() 解析 cli 选项

void main_dialog( int argc, char* argv[] )

    while ( (ch = getopt_long(argc, argv, "yscvh", longopts, NULL)) != -1 )
    switch (ch)
    
     ...
    

但是当我运行这个时,我的应用程序在调用 getopt_long() 时崩溃了。

我读到argv 的最终条目应该是null。所以对于参数 "foo bar" ,我们有 argc=2 ,但是 argv[0]="foo" , argv[1]="bar" 和 argv[2]='\0' 。在wordexp documentation 中几乎没有说明 we_wordv 的内部结构(即关于尾随 NULL 数组条目),所以我不知道这是否是问题所在。

这可能是问题的根源吗?是否还有另一个 glib 函数可以满足我的需求?

谢谢

【问题讨论】:

首先,getoptgetopt_long 期望实际参数从 argv[1] 开始。其次,终止条目是argv[argc],它是一个空指针,即NULLwordexp 为您添加。 还有,当crash发生时,argc的值和argv的内容是什么? 您之前是否使用过getopt()getopt_long()?如果是这样,您可能需要重新初始化它们,以便它们从当前参数列表的开头开始,而不是继续使用原始参数列表。如何进行重置并不总是很清楚。 BSD (Mac OS X) 文档:为了使用getopt() 计算多组参数,或多次计算一组参数,变量optreset 必须在第二个之前设置为1,并且对getopt() 的每组额外调用,以及变量optind 都必须重新初始化。 其他人则不需要。 @JonathanLeffler 宾果游戏!通读手册,它出现在 linux 中重置 getopt() 子系统必须通过 optind=0; 仍然让我走在正确的轨道上。您可能想将此添加为答案! 【参考方案1】:

将评论转移到答案 - 然后扩展想法。

在调用main_dialog()中的代码之前,您是否已经使用过getopt()getopt_long()?如果是这样,您可能需要重新初始化它们,以便它们从当前参数列表的开头开始,而不是继续使用原始参数列表。

如何进行重置并不总是很清楚。

BSD (Mac OS X) 文档:

为了使用getopt() 来评估多组参数,或多次评估单组参数,变量optreset 必须在第二个之前设置为1,并且每组额外调用@987654329 @,并且变量optind必须重新初始化。

它还记录了额外的变量:extern int optreset;

其他系统没有明确记录需要做什么。请注意,getopt() 的 POSIX 规范特别声明(强调已添加):

变量optind 是要处理的argv[] 向量的下一个元素的索引。系统应将其初始化为 1,并且getopt() 将在完成argv[] 的每个元素时对其进行更新。 如果应用程序在调用getopt() 之前将optind 设置为零,则行为未指定。argv[] 的元素包含多个选项字符时,未指定getopt() 如何确定哪些选项具有已经处理好了。

因此,您可能需要进行试验才能找到有效的方法。

从response comment 来看,有时设置optind = 0; 会起作用(这似乎适用于Linux)。目前尚不清楚它是否可以在任何地方工作(POSIX 说它可能不会);使用前测试!

你可以尝试这样一个程序,我称之为getopt-test.c

#include <stdio.h>
#include <unistd.h>

static void dump_getopt_state(const char *tag)

    printf("%s:\n", tag);
    printf("optind = %d, ", optind);
    printf("opterr = %d, ", opterr);
    printf("optopt = %d, ", optopt);
    printf("optarg = %p\n", (void *)optarg);


int main(int argc, char **argv)

    int opt;
    dump_getopt_state("Initial");
    char tag[32];

    while ((opt = getopt(argc, argv, "ab:cd:")) != -1)
    
        switch (opt)
        
        case 'a':
        case 'c':
            sprintf(tag, "Option %c", opt);
            break;
        case 'b':
        case 'd':
            sprintf(tag, "Option %c", opt);
            printf("Argument: %s\n", optarg);
            break;
        
        dump_getopt_state(tag);
    

    dump_getopt_state("Final");
    return 0;

示例输出(Mac OS X 10.10.4):

$ getopt-test
Initial:
optind = 1, opterr = 1, optopt = 0, optarg = 0x0
Final:
optind = 1, opterr = 1, optopt = 0, optarg = 0x0
$ getopt-test -a
Initial:
optind = 1, opterr = 1, optopt = 0, optarg = 0x0
Option a:
optind = 2, opterr = 1, optopt = 97, optarg = 0x0
Final:
optind = 2, opterr = 1, optopt = 97, optarg = 0x0
$ getopt-test -ac
Initial:
optind = 1, opterr = 1, optopt = 0, optarg = 0x0
Option a:
optind = 1, opterr = 1, optopt = 97, optarg = 0x0
Option c:
optind = 2, opterr = 1, optopt = 99, optarg = 0x0
Final:
optind = 2, opterr = 1, optopt = 99, optarg = 0x0
$ 

注意处理-aca选项后的状态。唯一可见的状态变化是在optopt 中,修改optopt 很可能不会触发重置。将optind 设置为零可能会奏效,但程序启动时optind 的官方值是1

这也是为什么全局变量不好的一个对象教训。

另请注意,如果您在调用getopt() 之间更改argcargv 的值,将发生什么情况尚不清楚。规范并没有说每次的值都应该相同,但是如果您不将代码重置为起点,则可能会出现混乱。

我的代码的输出格式可以改进为将optopt 打印为两位十六进制值。如果: 作为标签的一部分传递给转储函数可能会更好;那么第一个打印可能是:printf("%-.12s ", tag) 所以一切都对齐。 optarg 的值最有可能在长度上发生变化;这就是为什么它是最后的。

【讨论】:

不太可能optind = 0 是 linux 的手册页所建议的。请参阅“注释”部分linux.die.net/man/3/getopt @nass:这很好——这是一个合法的设计决策,可以记录在案。它不能保证在其他系统上工作,因此您必须阅读每个系统的手册页,并希望它实际记录在案。

以上是关于将 wordexp 的输出提供给 getopt_long 会使我的 linux cli 应用程序崩溃的主要内容,如果未能解决你的问题,请参考以下文章

如何将一个查询的输出提供给下一个查询

如何将一个查询的搜索输出作为值提供给另一个查询

如何将工艺(用于文本检测的字符区域感知)的输出提供给 crnn

如何将微调过的 bert 模型的输出作为输入提供给另一个微调过的 bert 模型?

如何将DynamoDB的Item.getNumberSet()输出提供给Item.withNumberSet()?

我们如何在 PyTorch 中将线性层的输出提供给 Conv2D?