Bash 的 IFS 变量如何影响命令替换?

Posted

技术标签:

【中文标题】Bash 的 IFS 变量如何影响命令替换?【英文标题】:How does Bash's IFS variable affect command substitution? 【发布时间】:2013-06-09 06:47:13 【问题描述】:

我正在编写一个 bash 脚本,该脚本循环遍历命令替换的输出,然后尝试在循环体中执行另一个命令替换。代码如下:

#!/usr/bin/bash

IFS=$'\n' 

for i in $( xmllint --xpath "string(/*[local-name()='Project'])" gsGDAL/gsGDAL.vcxproj.user )
do
   IFS=' ' #attempted with and without this line
   "$( awk -F= 'printf("export %s=\"%s\"", $1, $2)' <(echo $i) )"
   IFS=$'\n' #attempted with and without this line
done

我将 IFS 设置为换行符,以便循环遍历 xmllint 命令的每一行输出,而不是遍历每个以空格分隔的单词。 但是,这会使循环内的命令替换失败。一些调试得出结论,问题的本质是这样的:

#!/usr/bin/bash

IFS=$'\n'

$(echo export TEST="test")

给出错误:

./x.sh: line 6: export TEST=test: command not found

您可以看到我在第一个代码示例中尝试通过在循环中重置 IFS 来进行修复。那没用。

我意识到我可以使用脚本的不同习语来解决问题。 例如处理将 xmllint 命令替换为 awk 而不是在每个单独的行上执行 awk 来命名一种可能性。 欢迎对此发表评论,但请提交与以下相关的答案:

    为什么将 IFS 设置为换行符会混淆命令替换生成的导出? 为什么在循环中重置 IFS 不能解决问题?

更新: 根据与 Barmar 的讨论,IFS 用于命令/变量扩展后的分词。

【问题讨论】:

【参考方案1】:

我不认为IFS 是您的问题的原因。您的代码有两个问题:

    您将$(awk ...) 放在双引号中,因此不会进行分词。因此它将export varname="value" 视为命令的名称——空格是命令名称的一部分,而不是命令和参数之间的分隔符。

    即使没有双引号,它也不起作用,因为对命令替换的结果不进行引号处理,只进行分词和通配符扩展。因此,export 命令中的双引号将作为文字字符包含在所分配的值中。

正如 mplf 所指出的,解决这个问题的方法是使用eval

eval "$(awk -F= 'printf("export %s=\"%s\"", $1, $2)' <(echo $i) )"

【讨论】:

谢谢。我想知道您能否告诉我为什么将 IFS 设置为换行符会阻止命令替换结果正确执行。是不是因为shell使用IFS对命令进行token化,导致export TEST="test"在解析时被解释为单个token?而且,如果是这样,为什么 eval 不受此约束(我的代码在使用 eval 时无需将 IFS 重置为空格即可工作)? 是的,没错。解析命令行时,shell 使用分词来查找命令——它只是第一个单词。如果你改变分词的参数,它会影响这个。 确实如此,所以您仍然需要在eval 之前执行IFS=' ' 我发现它在不重置为 IFS='' 的情况下工作。而且,为了确定,我在运行脚本之前取消设置导出的值。 我的错误。 IFS仅用于命令/变量扩展后的分词,不用于解析命令本身。【参考方案2】:

使用eval 评估从子shell 返回的字符串。

IFS=' ' #attempted with and without this line
eval $( awk -F= 'printf("export %s=\"%s\"", $1, $2)' <(echo $i) )
IFS=$'\n' #attempted with and without this line

【讨论】:

以上是关于Bash 的 IFS 变量如何影响命令替换?的主要内容,如果未能解决你的问题,请参考以下文章

IFS简单说明

bash-read使用方法

bash-read使用方法

IFS和命令替换

x1fn.ifs命令的用途

[GXYCTF2019]Ping Ping Ping