为什么“ A = 10 echo $ A”不打印10?


25

该命令:

A=10 echo $A

打印一个空行。为什么不10呢?为什么就地临时环境设置不起作用?

我想知道原因和解释,而不是解决方案。

我用了

LANG=C gcc ...

强制gcc使用后备语言(英语)而不是系统语言(中文)。因此,我假设VAR=value前缀将为后面的命令设置临时环境。但是看起来我有点误会。

Answers:


22

这取决于评估命令的不同步骤的顺序。

A=10 echo $A首先将命令解析为由三个单词组成的简单命令A=10echo然后$A。然后,每个单词都经过变量替换,$A即将变量扩展转换为值(例如,省略了看不见的步骤)。

如果A具有值foo最初,扩张步骤的结果是仍具有三个词一个简单的命令:A=10echofoo。(外壳程序此时还记住哪些字符最初在引号中,在这种情况下,没有字符。)下一步是执行命令。由于A=10以有效的变量名开头,后跟等号,因此将其视为赋值。在执行命令期间,该变量在外壳程序和环境中均A设置为10。(通常,您需要编写在环境中export A具有A的属性,而不仅仅是将其作为shell变量;这是一个例外。)下一个单词不是赋值,因此将其视为命令名(这是内置命令)。的echo命令不依赖于任何变量,因此A=10 echo $A具有与完全相同的效果echo $A

如果仅在命令执行期间设置变量,但在执行命令时将分配考虑在内,则可以使用子外壳。用括号指示的子shell使所有状态更改(变量分配,当前目录,函数定义等)在该子shell本地。

(A=10; echo $A)

作出这样的export A=10,如果你想将变量导出到环境,以便它是由外部程序可见。


谢谢,我能说A=10 (echo $A)得到10吗?
Earth Engine

2
@EarthEngine不,那是语法错误。分配必须在简单命令的开头(即,仅是命令名称和一些参数,以及可选的一些初始分配和某些重定向)。A=10; (echo $A)输出,10但也A为脚本的其余部分设置。
吉尔(Gilles)'所以

2
@EarthEngine但是你可以说A=10 eval 'echo $A'。单引号将停止$A解释,直到对整行进行评估为止。此时A = 10。我认为这个答案比公认的更正确
奥利(Oli)

我认为这是正确的解释。发生这种情况的原因只是发生的扩展$A和的分配的顺序A。例如,不A=5; A=6 let 'a=A'; echo $a返回,并且我不认为启动子shell是因为它是内置命令。65let
David Ongaro 2014年

@EarthEngine:当它说,正确的解释是为了评价,它可能会误导:A=10 echo $A设置A=10任何命令之后,即使它们是在不同的线(时明确分配已经评价)。这与顺序无关,与范围
MestreLion 2014年

37

当您使用LANG=C gcc ... 时,shell 只会gcc的环境设置LANG ,而不会当前环境本身设置LANG (请参阅注释)。因此,完成后,将恢复为先前的值(或未设置)。gccLANG

另外,在使用A=10 echo $A它时,由外壳程序代替$ A而不是echo,并且此替换(称为“扩展”)发生对语句进行评估(包括赋值)之前,因此必须按预期A的值工作在当前环境中声明之前。

这就是为什么A=10 echo $A不能按预期工作的原因:A=10将为echo设置,但是echo内部忽略环境变量的值A。和$A替换为当前外壳程序中设置的值(无),然后将其作为参数传递给echo。

因此,您的假设是正确的:VAR=value command 确实有效,但这仅在command内部使用 VAR 时才有意义。如果没有,您仍然可以value作为参数传递给command,但是参数被当前的 shell 替换,因此必须在使用前进行设置:VAR=value; command "$VAR"

如果您知道如何创建可执行脚本,则可以尝试以下测试:

#!/bin/sh
echo "1st argument is $1"
echo "A is $A"

将其另存为testscript并尝试:

$ A=5; A=10 testscript "$A"; echo "$A"
1st argument is 5
A is 10
5

最后但并非最不重要的一点是,有必要了解shell环境变量以及程序参数之间的区别。

这里有一些很好的参考:

(*)注:技术上的外壳在当前的环境设置过,这里的原因:有些命令,比如echoreadtestshell内建的,因此它们不会产生一个子进程。它们在当前环境中运行。但是shell会注意分配只持续到命令运行为止,因此对于所有实际目的,效果是相同的:分配只能由该单个命令看到。


2
这种解释实际上是不正确的,尽管它会在少数情况下得出正确的结论。真正的解释是扩展的顺序:$A在分配发生之前进行评估。我认为您的解释只会在常规内置实用程序的情况下失败,这些实用程序的行为取决于变量的值:内置程序确实会看到分配的值。一个常见的示例是IFS=: read one two three rest,它读取以冒号分隔的字段:read内建函数确实看到的值IFS
吉尔(Gilles)'所以

“不适用于当前shell本身”是错误的:该变量在当前shell中设置,但仅在当前简单命令中有效。echo将看到价值10A,如果它的照顾。
吉尔斯(Gilles)'所以

@吉尔斯:非常感谢您的澄清!我不知道这种微妙之处。因此,如果我理解正确,bash 必须针对当前环境进行设置,否则buildins(不会产生新的pid)不会像其他“常规”命令那样看到分配。但是在完成命令以限制分配范围后,它会取消设置。这是正确的吗,我将相应地解决我的答案。PS:除了技术方面,我仍然认为答案应该侧重于范围方面,而不是评估顺序,否则人们会认为A=10 test; echo $A将打印10
MestreLion 2014年

3

执行您显然想要的一种可能的干净方法是发出命令:

A=10 eval 'echo $A'

这实际上会将值10的替换推迟到$ A的位置到以后的上下文(即,“已经”知道赋值的eval内部)。请注意,单引号必不可少。这样的构造干净利落地将分配传达给您所需的命令(在这种情况下为回声),而不会冒污染当前环境的风险。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.