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

  • “函数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环境变量,运行下面的代码都会产生相同的结果:
    C代码:
        #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,但可能不一致,或者这是一个错误。
    特别:
    第2229行开头的wordexp主体中,它确实获取了IFS环境变量值并对其进行处理:
    第2273-2276行:
         /* Find out what the field separators are.
           * There are two types: whitespace and non-whitespace.
           */
          ifs = getenv ("IFS");
    
    但是后来在函数中似乎没有
    使用$ IFS值进行单词分隔。
    除非第2273行上有“字段分隔符”,否则这看起来像个bug。
    第2396行上的“单词分隔符”表示不同的意思。
    第2395-2398行:
              default:
                /* Is it a word separator? */
                if (strchr (" \t", words[words_offset]) == NULL)
                {
    
    但是无论如何,代码似乎只使用空格或制表符作为分隔符
    不像bash那样尊重IFS设置拆分器值。
    问题
  • 我是否缺少某些内容,并且有一种方法可以让wordexp在除空格以外的其他字符上进行拆分?
  • 如果拆分仅在空格上,这是否是
  • gcc库实现或
  • 在Linux手册页的wordexp上使用
  • ,他们声称$ 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
    

    最佳答案

    您正在将苹果与桔子进行比较。 wordexp()以与shell相同的方式将字符串拆分为单个标记。 shell内置的read没有遵循相同的算法。它只是分词。您应该将wordexp()与如何解析脚本或shell函数的参数进行比较:

    #!/bin/sh
    
    printwords() {
        for arg in "$@"; do
            printf "%s\n" "$arg"
        done
    }
    
    echo "No IFS input: 1 2:3 4:5"
    printwords 1 2:3 4:5
    echo "IFS=':' input: 1 2:3 4:5"
    IFS=:
    printwords 1 2:3 4:5
    
    这产生
    No IFS input: 1 2:3 4:5
    1
    2:3
    4:5
    IFS=':' input: 1 2:3 4:5
    1
    2:3
    4:5
    
    就像C程序一样。

    现在,有趣的一点。我找不到快速扫描的POSIX文档中明确提到的内容,但是 bash manual可以说这是关于单词拆分的内容:

    让我们尝试一个在其参数中进行参数扩展的版本:
    #!/bin/sh
    
    printwords() {
        for arg in "$@"; do
            printf "%s\n" "$arg"
        done
    }
    
    foo=2:3
    printf "foo = %s\n" "$foo"
    printf "No IFS input: 1 \$foo 4:5\n"
    printwords 1 $foo 4:5
    printf "IFS=':' input: 1 \$foo 4:5\n"
    IFS=:
    printwords 1 $foo 4:5
    
    当通过像dashksh93bash这样的shell运行时(但除非您打开zsh选项,否则不通过SH_WORD_SPLIT运行)时,
    foo = 2:3
    No IFS input: 1 $foo 4:5
    1
    2:3
    4:5
    IFS=':' input: 1 $foo 4:5
    1
    2
    3
    4:5
    
    如您所见,具有参数的参数将进行字段拆分,但不进行文字拆分。对C程序中的字符串进行相同的更改,然后运行foo=2:3 ./wordexp将输出相同的内容。

    10-07 14:41