对于bash 的最佳参考(包括引用的工作原理)是 bash 手册本身,它几乎肯定安装在您的计算机上,您可以通过键入 man bash 在没有互联网连接的情况下阅读它。阅读量很大,但没有真正的替代品。
不过,我将尝试解释这个特定问题。有两件重要的事情需要知道:首先,bash 如何(以及何时)将命令行拆分为单独的“单词”(或命令行参数);第二,$@ 和 $* 是什么意思。这些并非完全无关。
分词部分由特殊参数IFS控制,但我只是提一下;我假设它没有被改变。详情请见man bash。
下面,我称用双引号 ("...") 弱引用 引用字符串,以及用撇号 ('...') 强引用 引用字符串。反斜杠 (\) 也是一种强引用形式。
发生分词:
在参数(shell 变量)被其值替换后,
只要有空白字符序列,
除非以任何方式引用空格,(" "、' '、\ 是三种方式),
在引号被删除之前。
一旦命令被分割成单词,第一个单词用于查找要调用的程序或函数,其余单词成为程序的参数。 (我忽略了很多东西,比如 shell 元字符、重定向、管道等等。更多详细信息,请参阅man bash。)
如果参数名称前面有$,则参数将替换为它们的值(步骤1),除非$name 被强引用(即'$name' 或\$name)。还有更多形式的参数替换。详情请见man bash。
现在,$@ 和 $* 都表示“当前命令/函数的所有位置参数”,如果它们不带引号使用,它们的作用完全相同。它们被所有位置参数替换,每个参数之间有一个空格。由于这是一种参数替换(如上所述),因此在替换之后会发生分词,除非替换是在引号中,如上面的列表。
如果替换在引号中,则根据上述规则,插入参数之间的空格不受分词的影响。这正是$* 的工作原理。 $* 替换为空格分隔的命令行参数,结果是分词; "$*" 被以空格分隔的命令行参数替换为一个单词。
"$@" 是一个例外。而且,事实上,这就是$@ 存在的原因。如果 $@ 位于弱引号 ("$@") 内,则引号将被删除,并且 每个位置参数 都被单独引用。然后将这些引用的位置参数隔开并替换$@。由于 $@ 本身不再被引用,因此插入的空格确实会导致分词。最终的结果是将各个参数保留为单个单词。
如果不完全清楚,这里有一个例子。 printf 的优点是可以重复提供的格式,直到参数用完为止,这样可以很容易地看到发生了什么。
showargs() {
echo -n '$*: '; printf "<%s> " $*; echo
echo -n '"$*": '; printf "<%s> " "$*"; echo
echo -n '"$@": '; printf "<%s> " "$@"; echo
}
showargs one two three
showargs "one two" three
(在执行之前尝试弄清楚打印的内容。)
人们常说,您几乎总是想要"$@",而几乎从不想要"$@" 或$*。这通常是正确的,但您也几乎从不想要"something with $@ inside of it"。要理解这一点,您需要知道 "something with $@ inside of it" 的作用。这有点奇怪,但不应该出乎意料。我们将以调用 OP 中的sbt 为例:
sbt "run-main com.longpackagename.mainclass $@ arg3"
为函数提供了两个位置参数,因此$1 是arg1,$2 是arg2。
首先,bash 删除了 $@ 周围的引号。但是,它不能完全删除它们,因为那里也有引用的文本。所以它必须关闭引用的文本,然后重新打开引用,产生:
sbt "run-main com.longpackagename.mainclass "$@" arg3"
现在,它可以替换为带引号的、以空格分隔的参数:
sbt "run-main com.longpackagename.mainclass ""arg1" "arg2"" arg3"
现在是分词:
sbt
"run-main com.longpackagename.mainclass ""arg1"
"arg2"" arg3"
并且引号被删除:
sbt
run-main com.longpackagename.mainclass arg1
arg2 arg3
sbt 只需要一个位置参数。你给了它两个,它忽略了第二个。
现在,假设使用单个参数"arg1 arg2" 调用该函数。在这种情况下,$@ 的替换会导致:
sbt "run-main com.longpackagename.mainclass ""arg1 arg2"" arg3"
分词产生
sbt
"run-main com.longpackagename.mainclass ""arg1 arg2"" arg3"
不带引号:
sbt
run-main com.longpackagename.mainclass arg1 arg2 arg3"
sbt 只有一个位置参数。