Bash中的$@与$*

在深入使用Linux的过程中,不可避免地要学习Shell编程。恰好最近在手动编写一个zsh主题,因此尝试着学习了一点有关Bash脚本的知识。

Shell语言的核心在于文本的粘贴,所有变量以及基于变量的操作实际上都是文本的编辑,其诡异的语法和执行逻辑往往会带来理解上的困难和安全隐患。这个系列将会持续地记录自己在学习Bash脚本的过程中的一些思考,或许会对同样困惑于此的读者有所帮助。

TL; DR

  • 多数情况下,在遍历数组元素也不希望因为空格等因素造成意外结果时,使用"$@"既可。
  • 全文是基于Bash的参数获取展开讨论的,Zsh有非常多的specifications,可能与上述内容相冲突。

$@和$*

在Bash脚本中,$@$*是用于获取脚本参数的两个特殊变量。根据Bash Beginner Guides$@$*在不出现在引号内时二者的行为完全相同:

$* - Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable.

$@ - Expands to the positional parameters, starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word.

《Bash Beginner Guides》

不带引号时,它们会被扩展为脚本的参数数组。值得注意的是,这种扩展的确切形式实际上是

1
$para1 $para2 $para3 ... $para4

此处变量之间的“空格”的语义并不是真实的空格,而是表示扩展的结果是一个变量数组。
对于数组变量的${arr[@]}${arr[*]},上述讨论依然成立。

""扩展

"" ''两种扩展的不同已经有很多详细的解释了,这里着重强调一个变量放在引号内与不放在引号内的不同。
当不放在引号内时,$var会经历split-glob过程,即

  • 首先根据环境变量IFS的值,分割字符串为多个子串
  • 将每个子串视作filename wildcard pattern,基于这些pattern去匹配文件名。如果匹配不到,则仍然为子串原本的内容。
1
2
3
4
5
#! /usr/bin/env bash
# 当前目录下有 foo.c bar.c foo.py
string="*.c *.o"
echo 'In "" : '"${string}"
echo 'Out of "": '${string}

输出为

1
2
In ""    : *.c *.o
Out of "": bar.c foo.c *.o

可见,不加引号时确实会进行split-glob。

$* $@ "$@""$*"

$@$*被置于引号内时,它们的扩展方式是不相同的。

  • "$@"会被扩展为"${para1}" "${para2}" ... "${paran}",即扩展结果仍为数组,但是其中的元素均使用双引号包裹,避免进行split-glob。
  • "$*"会被扩展为"${array[0]}${IFS}${array[1]}${IFS} ... ${IFS}${array[n]}",即使用$IFS连接各数组元素后的字符串,为单一变量。

一个例子

我们使用一个脚本来阐述上述的参数扩展方式的不同。

1
2
3
4
5
6
array=("a" "b" "c-d")
IFS="-"

for i in PATTERN; do
echo "${i}"
done

  • PATTERN=${array[@]}时,输出结果为
    1
    2
    3
    4
    a
    b
    c
    d
    因为不带引号时$@扩展为${array[0]} ${array[1]} ${array[2]},是一个数组,由于每一个元素都不带引号,因此array[2]会被split-glob,被分割成c和d。
  • PATTERN=${array[*]}时,输出结果为
    1
    2
    3
    4
    a
    b
    c
    d
    上文提到不用引号时$*$@行为一致,因此结果一致。
  • PATTERN="${array[@]}"时,输出结果为
    1
    2
    3
    a
    b
    c-d
    加上引号时"$@"的扩展结果为字面量的数组,因此最后一个"${array[2]}"不会被split-glob。
  • PATTERN="${array[*]}"时,输出结果为
    1
    a-b-c-d
    加上引号时,"$*"扩展为单一的字面量,因此结果为这个。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!