c - gcc wordexp Posix C 库函数不使用 Linux IFS 环境变量来拆分单词

标签 c linux gcc posix ifs

环境
操作系统:Ubuntu 20.4、Centos 8、macOS Catalina 10.15.7
语言:C、C++
编译器:gcc(每个操作系统的最新版本)
问题
我正在使用 wordexp Posix 库函数来获得类似 shell 的字符串扩展。
扩展工作正常,但有一个异常(exception):当我将 $IFS 环境变量设置为空格以外的其他值时,例如“:”,它似乎不会影响仅在空格上继续进行的单词的拆分,而不管 IFS 值如何.
重击测试
适用于 Linux 的 wordexp 手册页 https://man7.org/linux/man-pages/man3/wordexp.3.html状态:

  • “函数 wordexp() 对字符串执行类似 shell 的扩展...”
  • “字段拆分是使用环境变量 $IFS 完成的。如果未设置,字段分隔符为空格、制表符和换行符。”

  • 这就是为什么我希望 wordexp 在这方面表现得与 bash 相同。
    在所有列出的操作系统上,更改用于拆分的字符集时,我得到了完全正确和预期的结果:
    使用默认值(未设置 IFS)
        read -a words <<<"1 2:3 4:5"
        for word in "${words[@]}"; do echo "$word";  done
    
    在空间上正确拆分并产生结果:
        1
        2:3
        4:5
    
    将 IFS 设置为 ':'
        IFS=':' read -a words <<<"1 2:3 4:5"
        for word in "${words[@]}"; do echo "$word";  done
    
    在 ':' 上正确拆分并产生结果:
        1 2
        3 4
        5
    
    C代码测试
    但是无论是否设置 IFS 环境变量,运行下面的代码都会产生相同的结果:
    代码:
        #include <stdio.h>
        #include <wordexp.h>
        #include <stdlib.h>
        
        static void expand(char const *title, char const *str)
        {
            printf("%s input: %s\n", title, str);
            wordexp_t exp;
            int rcode = 0;
            if ((rcode = wordexp(str, &exp, WRDE_NOCMD)) == 0) {
                printf("output:\n");
                for (size_t i = 0; i < exp.we_wordc; i++)
                    printf("%s\n", exp.we_wordv[i]);
                wordfree(&exp);
            } else {
                printf("expand failed %d\n", rcode);
            }
        }
        
        int main()
        {
            char const *str = "1 2:3 4:5";
            
            expand("No IFS", str);
        
            int rcode = setenv("IFS", ":", 1);
            if ( rcode != 0 ) {
                perror("setenv IFS failed: ");
                return 1;
            }
        
            expand("IFS=':'", str);
        
            return 0;
        }
    
    所有操作系统的结果都是一样的:
        No IFS input: 1 2:3 4:5
        output:
        1
        2:3
        4:5
        IFS=':' input: 1 2:3 4:5
        output:
        1
        2:3
        4:5
    
    请注意,上面的代码片段是为这篇文章创建的 - 我使用更复杂的代码进行了测试,以验证环境变量确实设置正确。
    源代码审查
    我查看了 https://code.woboq.org/userspace/glibc/posix/wordexp.c.html 上可用的 wordexp 函数实现的源代码。并且它似乎确实使用了 $IFS,但可能不一致,或者这是一个错误。
    具体来说:
    在以 开头的 wordexp 正文中第 2229 行 它确实获取 IFS 环境变量值并对其进行处理:
    第 2273 - 2276 行:
         /* Find out what the field separators are.
           * There are two types: whitespace and non-whitespace.
           */
          ifs = getenv ("IFS");
    
    但是后来在函数中它似乎没有
    使用 $IFS 值进行单词分隔。
    这看起来像一个错误,除非 上的“字段分隔符”第 2273 行
    上的“单词分隔符”第 2396 行 意思不同。
    第 2395 - 2398 行:
              default:
                /* Is it a word separator? */
                if (strchr (" \t", words[words_offset]) == NULL)
                {
    
    但无论如何,代码似乎只使用空格或制表符作为分隔符
    与尊重 IFS 设置拆分器值的 bash 不同。
    问题
  • 我是否遗漏了一些东西,有没有办法让 wordexp 拆分为空格以外的字符?
  • 如果拆分仅在空格上,这是一个错误
  • gcc 库实现或
  • 在 wordexp 的 Linux 手册页中,他们声称 $IFS 可用于定义拆分器


  • 非常感谢您的所有评论和见解!
    答案摘要和解决方法
    在接受的答案中,有一个关于如何从 $IFS 实现非空白字符拆分的提示:您必须设置 $IFS 并将要拆分的字符串作为临时环境变量的值,然后调用wordexp 针对该临时变量。这在下面的更新代码中进行了演示。
    虽然在源代码中可见的这种行为实际上可能不是一个错误,但在我看来它绝对是一个有问题的设计决策......
    更新代码:
        #include <stdio.h>
        #include <wordexp.h>
        #include <stdlib.h>
        
        static void expand(char const *title, char const *str)
        {
            printf("%s input: %s\n", title, str);
            wordexp_t exp;
            int rcode = 0;
            if ((rcode = wordexp(str, &exp, WRDE_NOCMD)) == 0) {
                printf("output:\n");
                for (size_t i = 0; i < exp.we_wordc; i++)
                    printf("%s\n", exp.we_wordv[i]);
                wordfree(&exp);
            } else {
                printf("expand failed %d\n", rcode);
            }
        }
        
        int main()
        {
            char const *str = "1 2:3 4:5";
            
            expand("No IFS", str);
        
            int rcode = setenv("IFS", ":", 1);
            if ( rcode != 0 ) {
                perror("setenv IFS failed: ");
                return 1;
            }
        
            expand("IFS=':'", str);
            
            rcode = setenv("FAKE", str, 1);
            if ( rcode != 0 ) {
                perror("setenv FAKE failed: ");
                return 2;
            }
        
            expand("FAKE", "${FAKE}");    
        
            return 0;
        }
    
    产生的结果:
        No IFS input: 1 2:3 4:5
        output:
        1
        2:3
        4:5
        IFS=':' input: 1 2:3 4:5
        output:
        1
        2:3
        4:5
        FAKE input: ${FAKE}
        output:
        1 2
        3 4
        5
    

    最佳答案

    让我们天真地假设 POSIX 是可以理解的,并尝试使用它。我们拿 wordexp() from posix :

    The words argument is a pointer to a string containing one or more words to be expanded. The expansions shall be the same as would be performed by the command line interpreter if words were the part of a command line representing the arguments to a utility. [...]


    所以让我们转到“命令行解释器”。来自 posix shell command language :

    2.1 Shell Introduction

    [...]

    1. The shell breaks the input into tokens: words and operators; see Token Recognition. [.......]

    2.3 Token Recognition

    [...]

    1. If the current character is an unquoted <blank>, any token containing the previous character is delimited and the current character shall be discarded.
    2. If the previous character was part of a word, the current character shall be appended to that word.

    [...]


    基本上全2.3 Token Recognition部分适用于此处 - 这就是 wordexp()确实 - token 识别加上一些扩展。还有关于 field splitting 的最重要的东西,强调我的:

    After parameter expansion (Parameter Expansion), command substitution (Command Substitution), and arithmetic expansion (Arithmetic Expansion), the shell shall scan the results of expansions and substitutions that did not occur in double-quotes for field splitting and multiple fields can result.

    IFS影响字段拆分,它会影响 其他扩展的结果 被吐槽成文字。 IFS不影响字符串如何拆分为标记,它仍然使用 <blank> 拆分- 制表符或空格。所以你看到的行为。
    换句话说,当您输入 IFS=: 时在您的终端中,那么您就不会开始通过 IFS 分隔 token , 喜欢 echo:Hello:World ,但仍继续使用空格分隔命令的各个部分。
    无论如何,手册页是正确的......:p

    Am I missing something and there is a way to get wordexp to split on characters other than whitespace?


    不。如果您想在单词中有空格,请引用参数,就像在 shell 中一样。 "a b" "c d" "e" .

    If the split is only on whitespace, is this a bug in the


    无:p

    关于c - gcc wordexp Posix C 库函数不使用 Linux IFS 环境变量来拆分单词,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65822194/

    相关文章:

    c - GCC优化步骤

    C 程序-如何检查 Web 服务是否正在运行

    c - 构建内核模块并解决丢失的符号定义

    c++ - C/C++——在Linux下寻找实时计时事件

    linux - 如何在 nasm 上正确设置 execve 调用?

    gcc - ARM GCC 错误?使用 vldr 链而不是一个 vldmia…

    linux - 数据迁移并从 Bitnami GitLab 8.9.6 升级到最新的 GitLab CE 综合总线

    Python 3 将 ping 设置为变量返回 0

    c - 与 gcc 静态链接时如何只包含使用过的符号?

    cocoa - OS X 原生安装上可用的库列表?