繁体   English   中英

解决嵌套双引号中的 bash 环境变量的奇怪问题

[英]Strange issue resolving bash environmental variable in nested double quotes

我有一个需要在任意机器(可以是 Windows)上远程运行的设置脚本。 所以我有一些类似于 bash -c “做需要环境变量的事情”的东西。

我发现嵌套引号+环境变量发生了一些我不理解的奇怪事情(如下所示)

# This worked because my environment was polluted.
bash -c "NAME=me echo $NAME"
> me

# I think this was a weird cross platform issue with how I was running.
# I couldn't reproduce it locally.
bash -c "NAME=me echo "Hi $NAME""
> Hi $NAME

# This was my workaround, and I have no clue why this works.
# I get that "Start "" end" does string concatenation in bash,
# but I have no clue why that would make this print 'Hi me' instead
# of 'Hi'.
#
# This works because echo Hi name prints "Hi name". I thought echo only
# took the first argument passed in.
bash -c "NAME=me echo Hi "" $NAME"
> Hi me

# This is the same as the first case. NAME was just empty this time.
bash -c "NAME=me echo Hi $NAME"
> Hi

编辑:很多人指出,在 bash -c 运行之前,变量会在双引号中展开。 这是有道理的,但我觉得它并不能解释为什么案例 1 有效。

不应该bash -c "NAME=me echo $NAME"扩展到bash -c "NAME=me echo " ,因为在我们运行之前没有设置 NAME ?

编辑2:因为我的环境被污染了,所以这些东西起作用了。 我试图描述我在假设中犯了什么错误

这里至少有三个混淆来源:引号不(通常)嵌套, $variable引用被 shell 扩展,即使它们在双引号中,变量引用在var=value分配完成之前解析。

我先来看第二个问题。 这是一个显示效果的交互式示例:

$ NAME=Gordon
$ bash -c "NAME=me echo $NAME"
Gordon

在这里,外部(交互式)shell 在将$NAME传递给bash -c之前扩展了它,因此该命令本质上变成bash -c "NAME=me echo Gordon" 有几种方法可以避免这种情况:您可以转义$以删除其正常效果(但转义被删除,因此内部 shell 将看到它并正常应用它),或使用单引号而不是双引号(删除所有字符的特殊效果,除了结束单引号字符串的另一个单引号)。 所以让我们试试这些:

$ bash -c "NAME=me echo \$NAME"

$ bash -c 'NAME=me echo $NAME'

(您实际上看不到它,但是第二个命令之后也有一个空行,因为它也没有打印任何内容。)这里发生的是内部 shell (由bash -c创建的那个)确实得到了命令NAME=me echo $NAME ,但是在执行时它首先扩展$NAME (什么都不提供,因为它没有在那个 shell 中定义),然后执行NAME=me echo运行echo命令并将NAME设置为“me”它的环境。 让我们以交互方式尝试:

$ NAME=me echo $NAME
Gordon

(请记住,我之前在交互式 shell 中设置了NAME=Gordon 。)要获得预期的效果,您需要设置NAME ,然后作为单独的命令在echo命令中使用它:

$ bash -c "NAME=me; echo \$NAME"
me
$ bash -c 'NAME=me; echo $NAME'
me

好的,让我们继续讨论关于引用的原始问题。 正如我所说,引号(通常)不会嵌套。 要了解发生了什么,让我们分析一些示例命令。 您可以更好地了解 shell 如何使用set -x来解释事物,这使得 shell 在执行之前打印每个命令的等效项:

$ set -x
$ bash -c "NAME=me echo "Hi $NAME""
+ bash -c 'NAME=me echo Hi' Gordon
Hi

这里发生的是 shell 将"NAME=me echo "Hi解析为双引号字符串,后跟两个不带引号的字符; 由于它们之间没有间隙,因此它们被合并为bash -c的单个参数。 仅引用部分参数可能看起来有点奇怪,但在 shell 语法中实际上是完全正常的。 将单个参数的一部分不加引号,部分单引号,部分双引号,甚至是 ANSI-C 模式的一部分( $'ANSI-c-escaped stuff goes here' )甚至是正常的。

使用set -x , bash 将打印与正在执行的命令等效的内容。 所有这些命令在 shell 语法中是等效的:

bash -c "NAME=me echo "Hi Gordon
bash -c "NAME=me echo Hi" Gordon
bash -c 'NAME=me echo Hi' Gordon
bash -c NAME=me\ echo\ Hi Gordon
bash -c NAME=me' 'echo' 'Hi Gordon
bash -c 'NAME=me'\ "echo Hi" Gordon

...还有更多。 使用set -x , bash 将打印其中一个等价物,并且恰好选择在整个参数周围带有单引号的那个。

为了完整起见, $NAME""发生了什么? 它被视为一个不带引号的变量引用(扩展为Gordon ),紧随其后的是一个长度为零的双引号字符串,它根本不做任何事情。

但是......为什么只打印“嗨”? 好吧, bash -c将下一个参数视为要运行的命令,并将任何进一步的 arguments 作为该命令环境的参数向量( $0$1等)。 这是一个插图:

$ bash -c 'echo "Args: $0 $1 $2"' zeroth first second third
+ bash -c 'echo "Args: $0 $1 $2"' zeroth first second third
Args: zeroth first second

(“第三”没有被打印,因为该命令不打印$3 。)

因此,当您运行bash -c 'NAME=me echo Hi' Gordon时,它会执行NAME=me echo Hi并将$0设置为“Gordon”。

好的,这是我要看的最后一个例子:

$ bash -c "NAME=me echo Hi "" $NAME"
+ bash -c 'NAME=me echo Hi  Gordon'
Hi Gordon

这里发生的情况是,有一个双引号部分"NAME=me echo Hi "紧随其后的是另一个" $NAME" ,因此它们被合并为一个长参数(恰好连续包含两个空格 -第一个引用部分的一部分,第二部分的一部分)。 本质上,中间的""结束一个双引号部分并立即开始另一个,因此没有整体效果。 再一次,shell 决定打印单引号等效项,而不是其他各种可能的等效项。

那么我们如何才能真正让它正常工作呢? 以下是我实际推荐的:

$ bash -c 'NAME=me; echo "Hi $NAME"'
+ bash -c 'NAME=me; echo "Hi $NAME"'
Hi me

由于整个命令字符串都在单引号中,因此不会出现这些问题。 双引号只是作为参数的一部分传递的普通字符(因此双引号嵌套在单引号内 - 反之亦然 - 但实际上只是因为它们被忽略了),而$没有对外部 shell 也没有特殊含义。 哦,还有; 使这两个单独的命令,因此NAME=me部分可以在echo "$NAME"部分使用它之前生效。

另一个等价物是:

$ bash -c "NAME=me; echo \"Hi \$NAME\""
+ bash -c 'NAME=me; echo "Hi $NAME"'
Hi me

这里的转义删除了$和封闭的双引号的特殊含义。 请注意,shell 打印的内容与上次的set -x output 完全相同,表明这确实等同于单引号版本。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM