令人惊讶的阵列扩展行为

时间:2015-07-28 12:52:55

标签: arrays bash shell ifs

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

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函数。该函数有一个显示的字符串参数。为什么会发生其他事情?

如果您的答案得到手动/标准参考的支持,那就太酷了!

3 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:5)

这是因为$IFS设置为|

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

输出:

a b

man bash说:

  

IFS内部字段分隔符,用于扩展后的单词拆分...

答案 2 :(得分:3)

在[特殊参数[(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02)的POSIX规范部分中,我们找到了。

  

@

     

从一个开始扩展到位置参数。当扩展发生在双引号内,并且执行字段拆分(参见字段拆分)时,每个位置参数应作为单独的字段扩展,并规定第一个参数的扩展仍应与开头部分连接原始单词(假设扩展参数嵌入在单词中),最后一个参数的扩展仍然与原始单词的最后部分连接。如果没有位置参数,则扩展' @'即使在' @'是双引号。

     

*

     

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

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

我们看到*扩展"将[s]扩展为单个字段,每个参数的值由IFS变量"的第一个字符分隔。这就是您从a|becho "${X[*]"获得log2 "${X[*]}"的原因。

我们还看到@扩展扩展为"每个位置参数将作为单独的字段扩展"。这就是您从a becho "${X[@]}"获得log2 "${X[@]}"的原因。

您是否在规范文本中看到有关字段拆分的说明? "执行字段拆分(参见Field Splitting)&#34 ;?这就是这里神秘的关键。

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

显示问题的最简单方法是在启用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被调用的时间| 已经消失

它已经消失的原因是因为没有引号变量扩展的结果(在这种情况下,*扩展)是字段/单词拆分。由于IFS使用两者来组合要展开的字段,然后再将它们拆分,|会被字段拆分吞噬。

要完成解释(对于实际存在问题的情况),即使调用中的扩展的引用版本(log1扩展为{{log1 "${X[*]}"log1 "a|b"失败的原因也是如此1}}正确)是因为log1 本身不使用@的引用扩展,因此函数中@的扩展本身是单词分割的( echo a b案例中的log1以及所有其他log1案例都可以看到。