为什么Shell脚本中的引号与Shell命令中的引号行为不同?

时间:2019-01-13 18:19:48

标签: linux bash shell ubuntu

我在Windows 10和bash上使用WSL(Ubuntu 18.04)。

我有一个文件filename.gpg,内容如下:

export SOME_ENV_VAR='123'

现在我运行以下命令:

$ $(gpg -d filename.gpg)
$ echo $SOME_ENV_VAR
'123' <-- with quotes

但是,如果我直接在外壳中运行它:

$ export SOME_ENV_VAR='123'
$ echo $SOME_ENV_VAR
123 < -- without quotes

为什么会这样?为什么使用$()运行命令和直接运行命令之间有区别?

此外:我使用eval $(gpg -d filename)可以正常工作,我不知道为什么会这样。

1 个答案:

答案 0 :(得分:4)

shell脚本中的报价与shell命令中的报价没有什么不同。

使用$(gpg -d filename.gpg)语法,您不是在执行Shell脚本,而是执行常规的单个命令。

您的命令做什么

  1. 它执行gpg -d filename.gpg
  2. 从结果中,它以第一个(IFS分隔)单词作为要执行的命令
  3. 它将个其他(以IFS分隔)的单词(包括其他行中的单词)作为其参数
  4. 执行命令

从以下实际示例中,您可以看到它与执行shell脚本有何不同:

  1. 从filename.gpg中删除导出一词:然后该命令为SOME_ENV_VAR='123',这不能理解为变量赋值(您将获得SOME_ENV_VAR='123': command not found)。
  2. 如果添加多行,它们将不会被理解为单独的命令行,而是被视为第一个命令(export)的参数。
  3. 如果将export SOME_ENV_VAR='123'更改为export SOME_ENV_VAR=$PWD,则SOME_ENV_VAR将不包含变量PWD的内容,而是字符串$var

为什么会这样?

分析命令时,请参见how bash performs expansion

有很多步骤。 $(...)被称为“命令替换”,是第四步。完成后,将不再执行之前的任何步骤。这就解释了为什么删除export字时您的命令不起作用,以及为什么变量没有替换成结果。

此外,“引用删除”是最后一步,the manual reads

  

所有未加引号的字符“ \”,“'”和“”   并非由于上述任一扩展而导致的结果

由于单引号是由“命令替换”扩展产生的,因此不会被删除。这就是SOME_ENV_VAR的内容为'123'而不是123的原因。

eval为什么起作用?

因为eval触发了其参数的另一个完整解析。整个扩展集将再次运行。

From the manual

  

参数被合并为一个命令,然后被读取并执行

请注意,这意味着您仍在运行一个单个命令,而不是Shell脚本。如果您的filename.gpg脚本有几行,则随后的行将被添加到第一个(也是唯一的)命令的参数列表中。

那我该怎么办?

只需将sourceprocess substitution一起使用。

source <(gpg -d filename.gpg)

eval相反,source用于在当前上下文中执行Shell脚本。进程替换提供了一个伪文件名,其中包含替换结果(即gpg的输出)。