我在 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
但是,如果我直接在 shell 中运行它:
$ export SOME_ENV_VAR='123'
$ echo $SOME_ENV_VAR
123 < -- without quotes
为什么会这样?为什么使用 $()
运行命令和直接运行命令有区别?
另外:我使用 eval $(gpg -d filename)
让它工作,我不知道为什么会这样。
最佳答案
shell 脚本中的引号与 shell 命令中的引号没有区别。
使用 $(gpg -d filename.gpg)
语法,您不是在执行 shell 脚本,而是在执行常规的单个命令。
你的命令做什么
- 它执行
gpg -d filename.gpg
- 从结果中,它以第一个(IFS 分隔的)单词作为要执行的命令
- 它采用所有其他(IFS 分隔的)单词,包括来自其他行的单词,作为其参数
- 它执行命令
从下面的实际例子中,你可以看出它与执行shell脚本有何不同:
- 从 filename.gpg 中删除 export 一词:命令是
SOME_ENV_VAR='123'
,它不被理解为变量赋值(您将得到SOME_ENV_VAR='123':命令未找到
)。 - 如果您添加多行,它们将不会被理解为单独的命令行,而是被理解为第一个命令(
export
)的参数。 - 如果将
export SOME_ENV_VAR='123'
更改为export SOME_ENV_VAR=$PWD
,SOME_ENV_VAR 将不包含变量 PWD 的内容,而是包含字符串$变量
为什么会这样?
参见 how bash performs expansion分析命令时。
有很多步骤。 $(...)
称为“命令替换”,是第四步。完成后,不会再执行前面的任何步骤。这解释了为什么当您删除 export
词时您的命令不起作用,以及为什么没有在结果中替换变量。
此外,“引用删除”是最后一步,the manual reads :
all unquoted occurrences of the characters ‘\’, ‘'’, and ‘"’ that did not result from one of the above expansions are removed
由于单引号是由“命令替换”扩展产生的,因此它们没有被删除。这就是为什么 SOME_ENV_VAR 的内容是 '123'
而不是 123
。
为什么 eval
有效?
因为eval触发了对其参数的又一次完整解析。再次运行整套扩展。
The arguments are concatenated together into a single command, which is then read and executed
请注意,这意味着您仍在运行一个 单个命令,而不是 shell 脚本。如果您的 filename.gpg
脚本有几行,后续行将添加到第一个(也是唯一一个)命令的参数列表中。
那我该怎么办?
只需使用 source
和 process substitution .
source <(gpg -d filename.gpg)
与eval
相反,source
用于在当前上下文中执行shell脚本。进程替换提供了一个包含替换结果的伪文件名(即 gpg
的输出)。
关于linux - 为什么 shell 脚本中的引号与 shell 命令中的引号行为不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54171835/