环境
操作系统: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在这方面的行为与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设置拆分器值。
问题
非常感谢您的所有评论和见解!
答案摘要和解决方法
在已接受的答案中,有一个关于如何从$ 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
当通过像dash
,ksh93
或bash
这样的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
将输出相同的内容。