访问bash命令行参数$@与$*的区别

439

在许多 Stack Overflow 问题和 Bash 教程中,我看到可以通过两种方式访问 Bash 脚本中的命令行参数:

$ ~ >cat testargs.sh 
#!/bin/bash

echo "you passed me" $*
echo "you passed me" $@

导致结果是:

$ ~> bash testargs.sh arg1 arg2
you passed me arg1 arg2
you passed me arg1 arg2

$*$@ 有什么区别?
何时应使用前者,何时应使用后者?


1
请看这个答案:https://dev59.com/B3RA5IYBdhLWcg3wvgsb#842325 - codeling
在IntelliJ中的静态分析将echo "something $@"视为错误。 - Alex Cohn
5个回答

542

当特殊参数被引用时,差异就出现了。让我举个例子来说明这些区别:

$ set -- "arg  1" "arg  2" "arg  3"

$ for word in $*; do echo "$word"; done
arg
1
arg
2
arg
3

$ for word in $@; do echo "$word"; done
arg
1
arg
2
arg
3

$ for word in "$*"; do echo "$word"; done
arg  1 arg  2 arg  3

$ for word in "$@"; do echo "$word"; done
arg  1
arg  2
arg  3

再举一个引号的重要性的例子:请注意,“arg”和数字之间有2个空格,但如果我没有引用 $word:

$ for word in "$@"; do echo $word; done
arg 1
arg 2
arg 3

在bash中,"$@"是默认的列表迭代:

$ for word; do echo "$word"; done
arg  1
arg  2
arg  3

7
是否存在 $*"$*" 必须使用的可能用例,而 $@"$@" 无法实现其目的? - anishsane
9
哪个版本更适合用于“包装器”脚本,其中脚本参数需要成为一个新命令的参数? - Segfault
14
在这种情况下,始终选择带引号的 "$@" - glenn jackman
5
这个答案包含了有用的例子,但如果能解释背后的机制会更好。为什么会产生这样的结果? - Lii
2
Bash是一种奇怪的语言。在很多方面,我认为它受到与40多年历史的Bourne shell兼容性的限制。 - glenn jackman
显示剩余3条评论

358

来自Bash Hackers Wiki的一个非常方便的概述表:

语法 有效结果
$* $1 $2 $3 … ${N}
$@ $1 $2 $3 … ${N}
"$*" "$1c$2c$3c…c${N}"
"$@" "$1" "$2" "$3" … "${N}"
在第三行中,c$IFS 的第一个字符,代表了输入字段分隔符,它是一个 shell 变量。
如果要存储这些参数,可以将它们加载到一个数组变量中。点击此处了解更多信息。

1
这里有一个示例,包括引用的输入。输入也很重要!链接 - Serge Stroobandt
2
假设我想创建一个包装脚本,仅模仿被包装命令的功能。我应该使用哪种语法从包装脚本传递参数到内部命令? - Marinos An
4
@MarinosAn 使用 "$@"(带引号)。其他变体都不起作用。 - Gavin Haynes
1
非常感谢您提供如此清晰的解释 - 如果需要一份晦涩难懂的解释,请参阅 https://www.gnu.org/software/bash/manual/html_node/Special-Parameters.html Bash 文档。 - mbigras

53

$*

按位置参数展开,起始位置为1。当在双引号内进行扩展时,它会展开为一个单词,每个参数的值由IFS特殊变量的第一个字符分隔。也就是说"$*"等同于"$1c$2c...",其中c是IFS变量值的第一个字符。如果未设置IFS,则使用空格分隔参数。如果IFS为空,则参数将被连接而不插入分隔符。

$@

按位置参数展开,起始位置为1。当在双引号内进行扩展时,每个参数都会展开为一个单独的单词。也就是说"$@"等同于"$1" "$2"...如果双引号内进行扩展时,第一个参数的展开结果将与原始单词的前半部分连接,最后一个参数的展开结果将与原始单词的后半部分连接。当没有位置参数时,"$@"和$@展开为空(即被移除)。

来源:Bash man


24

$@与$*相同,但每个参数都是一个带引号的字符串,也就是说,这些参数会原样传递而没有任何解释或扩展。这意味着,在参数列表中的每个参数被视为独立的单词。

当然,“$@”应该加上引号。

http://tldp.org/LDP/abs/html/internalvariables.html#ARGLIST


3

这个例子可以帮助我们区分“at”和“asterix”的使用方式。我声明了两个数组“fruits”和“vegetables”。

fruits=(apple pear plumm peach melon)            
vegetables=(carrot tomato cucumber potatoe onion)

printf "Fruits:\t%s\n" "${fruits[*]}"            
printf "Fruits:\t%s\n" "${fruits[@]}"            
echo + --------------------------------------------- +      
printf "Vegetables:\t%s\n" "${vegetables[*]}"    
printf "Vegetables:\t%s\n" "${vegetables[@]}"    

看看上面代码的结果:
Fruits: apple pear plumm peach melon
Fruits: apple
Fruits: pear
Fruits: plumm
Fruits: peach
Fruits: melon
+ --------------------------------------------- +
Vegetables: carrot tomato cucumber potatoe onion
Vegetables: carrot
Vegetables: tomato
Vegetables: cucumber
Vegetables: potatoe
Vegetables: onion

12
从科学角度来看,番茄是水果。 - Randy
1
你是正确的!“在植物学中,果实是指在开花后从子房形成的、带有种子的结构,指的是被称为被子植物的开花植物。” https://zh.wikipedia.org/wiki/%E7%B5%90%E6%9E%9C - stefansson
@Randy:从科学角度来看,所有的水果都是蔬菜(这是“植物”的同义词)。 - Cris Luengo
@CrisLuengo 异端邪说! :) - Randy

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