假设我有以下 bash 脚本:
#/bin/sh
#next line will work correctly, outputting user name and current directory
start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'
MYVAR="start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'"
#next line will fail with following error:
#ls': -c: line 0: unexpected EOF while looking for matching `''
$MYVAR
问题 - 为什么通过变量的第二种方法不起作用? 如何使其起作用?
答案1
正如@lled所解释的那样,问题在于shell在扩展变量之前会处理引号,因此将引号放在变量中没有任何用处。但是,有几种替代方案,具体取决于您想要存储命令的原因(而不是直接执行命令)。
如果您只想定义一次命令,然后重复使用它,请使用函数:
myfunc() {
start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls'
}
# ...
myfunc
如果您需要在一个地方构建/选择命令,然后在另一个地方使用它,则可以使用 bash 数组来存储它。将命令的每个“单词”存储为数组元素,然后如果您正确引用它,这些单词分隔符将被保留:
myarray=(start-stop-daemon --start --exec /bin/su -- root -c 'whoami; ls')
# ...
"${myarray[@]}"
这里发生的事情是,数组被定义为具有元素“start-stop-daemon”、“--start”、“--exec”、“/bin/su”、“--”、“root”、“-c”和“whoami; ls”。单引号不作为数组的一部分存储,但它们的作用是使“whoami; ls”成为单个数组元素。然后,在数组的扩展中,告诉[@]
shell 将每个数组元素扩展为一个单独的单词,而双引号可以防止在结果值上进行任何额外的单词拆分。
有关更多信息(以及其他一些选项),请参阅BashFAQ #50:我试图将命令放入变量中,但复杂的情况总是失败!
答案2
Bourne(以及再次 Bourne)shell 具有从行解析到命令执行的复杂规则。
为了简化起见,我们将直接命令行简化为
su -c 'whoami; ls'
以及你的“嵌入式”
MYVAR="su -c 'whoami; ls'"
$MYVAR
在第一种情况下,命令行被分成 3 个标记(单词),因为空格字符是分隔单词的元字符,也因为引号会转义元字符(因此将空格转义为引号)。在命令执行之前,会应用引号删除阶段,留下 3 个单词,没有引号。第一个单词是命令名称su
,其他两个单词是-c
和whoami; ls
,即命令参数。对。
在第二种情况下,调用由单个参数扩展组成。对于某些扩展(包括参数扩展),会应用分词。为此,使用名为 IFS 的特殊变量从扩展的参数值中推断出单词。默认情况下,IFS 由空格和制表符以及换行符组成。这意味着这些字符中的每一个都用于拆分一串字符以构成单词。这里,扩展值为:
su -c 'whoami; ls'
我们最终得到4单词,即:su
、-c
和。最重要的是,对于参数扩展值,不会发生引号删除阶段。发出命令(带有命令名称'whoami;
及其3 个参数),然后您会收到一条奇怪的错误消息。ls'
su
这种情况的复杂性在于我们必须将包含空格的单词作为的参数su
,而空格本身会暴露给单词拆分阶段,并且引号在参数扩展中是无用的。
解决这个问题的办法是使用 IFS 变量。一种解决方案是:
IFS=: MYVAR="su:-c:whoami; ls"
$MYVAR
这里覆盖了 IFS,以将空格字符排除在单词拆分之外,该任务由新引入的冒号字符承担。