【发布时间】:2021-04-25 13:33:17
【问题描述】:
环境
操作系统:Ubunty 20.4、Centos 8、macOS Catalina 10.15.7
语言:C、C++
编译器:gcc(每个操作系统的最新版本)
问题
我正在使用 wordexp Posix 库函数来获得类似 shell 的字符串扩展。
扩展工作正常,但有一个例外:当我将 $IFS 环境变量设置为除空格以外的其他内容时,例如“:”,它似乎不会影响仅在空格上继续执行的单词的拆分,而不管 IFS 值如何.
bash 测试
wordexp for Linux 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 环境变量,运行下面的代码都会产生相同的结果:
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
请注意,上面的 sn-p 是为这篇文章创建的 - 我确实使用更复杂的代码进行了测试,验证了环境变量确实设置正确。
源代码审查
我查看了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 行 上有“字段分隔符”
和 第 2396 行上的“单词分隔符”表示不同的含义。
第 2395 - 2398 行:
default:
/* Is it a word separator? */
if (strchr (" \t", words[words_offset]) == NULL)
{
但无论如何,代码似乎只使用空格或制表符作为分隔符 不像 bash,它尊重 IFS 集拆分器值。
问题
- 我是否遗漏了什么,有办法让 wordexp 分割除空格以外的字符吗?
- 如果仅在空白处进行拆分,这是否是
- gcc 库实现或
- 在 wordexp 的 Linux 手册页中,他们声称 $IFS 可用于定义拆分器
非常感谢您的所有 cmets 和见解!
答案摘要和解决方法
在接受的答案中,有一个关于如何从 $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
【问题讨论】:
-
我为我正在查看的源发布的链接声称适用于 glibc,这就是 GNU C 库的名称。所以我认为这是实际的代码(也许不是最新的)。你有理由认为它不是吗?无论如何,我观察到的行为与该代码非常匹配......是的,我可以尝试不同的编译器,但我仅限于 gcc - 这是一个很长的故事。但感谢您对此进行调查。
-
据我所知——你是对的。这看起来很像一个错误。我添加了对
getenv ("IFS")的调用,以确认环境看到setenv()调用——确实如此。除非我遗漏了什么,否则您的用法是正确的。gcc (GCC) 10.2.0和gcc (SUSE Linux) 7.4.1的行为相同。 (这个问题写得非常好) -
我 99% 确定您将标记化混淆为单词,并在解析的后期将这些标记中的扩展结果拆分,但我无法挖掘相关参考并举出一些例子。
wordexp()与内置的read不同。 -
wordexp 只执行 shell 命令行语法,而不是一般的字符串拆分。为此使用 strtok。