【问题标题】:Surprising array expansion behaviour令人惊讶的阵列扩展行为
【发布时间】:2015-10-19 01:04:50
【问题描述】:

我对以下示例中标记为 (!!) 的行感到惊讶:

log1 () { echo $@; }
log2 () { echo "$@"; }

X=(a b)
IFS='|'

echo ${X[@]}   # prints a b
echo "${X[@]}" # prints a b
echo ${X[*]}   # prints a b
echo "${X[*]}" # prints a|b
echo "---"
log1 ${X[@]}   # prints a b
log1 "${X[@]}" # prints a b
log1 ${X[*]}   # prints a b
log1 "${X[*]}" # prints a b (!!)
echo "---"
log2 ${X[@]}   # prints a b
log2 "${X[@]}" # prints a b
log2 ${X[*]}   # prints a b
log2 "${X[*]}" # prints a|b

这是我对行为的理解:

  • ${X[*]}${X[@]} 都扩展为 a b
  • "${X[*]}" 扩展为 "a|b"
  • "${X[@]}" 扩展为 "a" "b"
  • $*$@${X[*]}${X[@]} 具有相同的行为,只是它们的内容是程序或函数的参数

bash manual似乎证实了这一点。

因此,在log1 "${X[*]}" 行中,我希望引用的表达式扩展为“a|b”,然后传递给 log1 函数。该函数有一个显示的字符串参数。为什么会发生其他事情?

如果您的答案有手册/标准参考资料支持,那就太好了!

【问题讨论】:

    标签: arrays bash shell ifs


    【解决方案1】:

    那是因为$IFS 设置为|

    (X='a|b' ; IFS='|' ; echo $X)
    

    输出:

    a b
    

    man bash 说:

    IFS 内部字段分隔符,用于展开后分词...

    【讨论】:

      【解决方案2】:

      在 [Special Parameters[(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02) 的 POSIX 规范部分,我们可以找到。

      @

      从一开始扩展到位置参数。当扩展发生在双引号内时,并且在执行字段拆分(请参阅字段拆分)时,每个位置参数都应扩展为一个单独的字段,前提是第一个参数的扩展仍应与开头部分连接原词(假设扩展参数嵌入一个词中),最后一个参数的扩展仍应与原词的最后部分连接。如果没有位置参数,“@”的扩展将生成零字段,即使“@”被双引号括起来。

      *

      从一开始扩展到位置参数。当扩展发生在双引号字符串中时(请参阅双引号),它应扩展为单个字段,每个参数的值由 IFS 变量的第一个字符分隔,或者如果 IFS 未设置,则由 a 分隔。如果 IFS 设置为空字符串,这不等于取消设置它;它的第一个字符不存在,所以参数值是串联的。

      所以从引用的变体开始(它们更简单):

      我们看到* 扩展“将[s] 扩展为单个字段,每个参数的值由 IFS 变量的第一个字符分隔”。这就是为什么你会从echo "${X[*]"log2 "${X[*]}" 得到a|b

      我们还看到@ 扩展扩展为“每个位置参数都应扩展为单独的字段”。这就是为什么你会从echo "${X[@]}"log2 "${X[@]}" 得到a b

      您是否在规范文本中看到有关字段拆分的说明? “在哪里执行字段拆分(请参阅字段拆分)”?这就是解开谜团的关键。

      在引号之外,扩展的行为是相同的。不同之处在于之后会发生什么。具体来说,字段/单词拆分。

      显示问题的最简单方法是在启用set -x 的情况下运行您的代码。

      这会让你得到这个:

      + X=(a b)
      + IFS='|'
      + echo a b
      a b
      + echo a b
      a b
      + echo a b
      a b
      + echo 'a|b'
      a|b
      + echo ---
      ---
      + log1 a b
      + echo a b
      a b
      + log1 a b
      + echo a b
      a b
      + log1 a b
      + echo a b
      a b
      + log1 'a|b'
      + echo a b
      a b
      + echo ---
      ---
      + log2 a b
      + echo a b
      a b
      + log2 a b
      + echo a b
      a b
      + log2 a b
      + echo a b
      a b
      + log2 'a|b'
      + echo 'a|b'
      a|b
      

      这里要注意的是,当log1 被调用时,除了最后的情况之外,|已经消失了

      它已经消失的原因是因为没有引号,变量扩展的结果(在本例中为* 扩展)是字段/单词拆分。由于IFSboth 用于合并正在扩展的字段,然后再次拆分它们,| 被字段拆分所吞噬。

      为了完成解释(对于实际问题的情况),即使在调用中引用了扩展版本(即 log1 "${X[*]}" 正确扩展为 log1 "a|b"),log1 失败的原因是因为log1 itself 不使用@ 的引用扩展,所以函数中@ 的扩展本身就是分词的(echo a b 可以看出@987654344 @case 以及所有其他 log1 case)。

      【讨论】:

        【解决方案3】:

        IFS 不仅用于连接${X[*]} 的元素,还用于拆分不带引号的扩展$@。对于log1 "${X[*]}",会发生以下情况:

        1. "${X[*]}" 按预期扩展为a|b,因此$1log1 内设置为a|b
        2. $@(未加引号)展开时,结果字符串为a|b
        3. 不带引号的扩展以| 作为分隔符进行分词(由于IFS 的全局值),因此echo 接收两个参数,ab

        【讨论】:

        • 哦。正确的。有没有办法产生我试图拥有的行为(即使用分隔符进行格式化,但不进行分词)?我认为printf 可能是最简单的选择(例如:stackoverflow.com/questions/12985178
        • 始终使用带引号的扩展。这就是答案。您的 log1 函数完全不正确。 log2 是正确的形式。
        • 更准确地说,总是引用$@。 (在某些极端情况下,您可能会故意不引用 $* 或其他参数,但要引用 $@ 存在;否则它与 $* 相同。)
        猜你喜欢
        • 2019-10-05
        • 1970-01-01
        • 2021-08-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-27
        • 2012-08-11
        • 1970-01-01
        相关资源
        最近更新 更多