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