环境
操作系统: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 在这方面表现得与 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 不同。
问题
非常感谢您的所有评论和见解!
答案摘要和解决方法
在接受的答案中,有一个关于如何从 $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
[...]
- The shell breaks the input into tokens: words and operators; see Token Recognition. [.......]
2.3 Token Recognition
[...]
- If the current character is an unquoted <blank>, any token containing the previous character is delimited and the current character shall be discarded.
- 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/