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

3
我正在编写一段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命令的每个输出“LINE”,而不是遍历每个以空格分隔的单词。 然而,这使得循环内的命令替换失败。一些调试工作得出结论,问题的本质在于:
#!/usr/bin/bash

IFS=$'\n'

$(echo export TEST="test")

出现以下错误:

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

您可以看到,在第一个代码示例中,我尝试通过在循环内部重置IFS来修复问题。但那并没有起作用。
我意识到我可以使用不同的脚本风格来解决这个问题。
例如,将xmllint命令的进程替换为awk而不是对每个单独行执行awk等等。 欢迎您提出相关评论,但请提交与以下相关的答案:
  1. 为什么将IFS设置为换行符会损坏命令替换生成的导出项?
  2. 为什么在循环中重置IFS不能修复问题?

更新: 根据与Barmar的讨论,IFS用于命令/变量扩展后进行词拆分。
2个回答

4
我认为IFS不是你的问题的原因。你的代码有两个问题:
  1. 你把$(awk ...)放在双引号中,所以没有进行单词拆分。它将export varname="value"视为命令的名称--空格是命令名称的一部分,而不是命令和参数之间的分隔符。

  2. 即使没有双引号,也无法工作,因为不会对命令替换结果执行引号处理,只会执行单词拆分和通配符扩展。所以你的export命令中的双引号将被包含在被赋值的值中。

如mplf所指出的,解决方法是使用eval

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

谢谢。我想知道为什么将IFS设置为换行符会阻止命令替换结果正常执行。这是因为shell使用IFS对命令进行标记化,导致在解析期间将export TEST="test"解释为单个标记吗?如果是这样,那么为什么eval不受此影响(当我使用eval时,我的代码无需将IFS重置为空格即可正常工作)? - user2141130
是的,没错。在解析命令行时,shell使用单词分割来查找命令——它只是第一个单词。如果更改单词分割的参数,则会影响此功能。 - Barmar
它确实需要在eval之前执行IFS=' ' - Barmar
我发现它在不重置IFS=' '的情况下就能正常工作。为了确保,我在运行脚本之前取消设置导出的值。 - user2141130
我的错误。IFS 仅用于命令/变量扩展后的单词拆分,而不是解析命令本身。 - Barmar

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接