104

考虑这个片段:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz

在这里,我在第一行设置$SOMEVARAAA- 当我在第二行回显它时,我得到了AAA预期的内容。

但是,如果我尝试在与以下相同的命令行上指定变量echo

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz

...我没有得到BBB预期的结果-我得到了旧值(AAA)。

事情应该是这样的吗?如果是这样,那你怎么能指定变量LD_PRELOAD=/... program args ...并让它工作呢?我错过了什么?

4

9 回答 9

111

你看到的是预期的行为。问题是父 shell$SOMEVAR在使用修改后的环境调用命令之前在命令行上进行评估。您需要$SOMEVAR在设置环境之前获得延迟的评估。

您的直接选择包括:

  1. SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz.
  2. SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'.

这两个都使用单引号来防止父 shell 评估$SOMEVAR; 仅在环境中设置后才对其进行评估(暂时,在单个命令的持续时间内)。

另一种选择是使用子外壳符号(Marcus Kuhn在他的回答中也建议):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

该变量仅在子shell中设置

于 2012-06-07T19:24:36.403 回答
41

问题,重新审视

坦率地说,手册在这一点上令人困惑。GNU Bash 手册说:

任何简单命令或函数的环境 [注意,这不包括内置函数] 可以通过在其前面加上参数分配来临时扩充,如 Shell 参数中所述。这些赋值语句只影响该命令看到的环境。

如果你真的解析这句话,它的意思是修改了命令/函数的环境,而不是修改了父进程的环境。因此,这将起作用:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb

因为 env 命令的环境在执行之前已经被修改过。但是,这不起作用:

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc

因为当shell执行参数扩展时。

解释器步骤

问题的另一部分是 Bash为其解释器定义了这些步骤:

  1. 从文件(参见 Shell 脚本)、作为参数提供给 -c 调用选项(参见 Invoking Bash)的字符串或用户终端读取其输入。
  2. 将输入分解为单词和运算符,遵守引用中描述的引用规则。这些标记由元字符分隔。通过此步骤执行别名扩展(请参阅别名)。
  3. 将标记解析为简单和复合命令(请参阅 Shell 命令)。
  4. 执行各种 shell 扩展(请参阅 Shell 扩展),将扩展的标记分解为文件名列表(请参阅文件名扩展)以及命令和参数。
  5. 执行任何必要的重定向(请参阅重定向)并从参数列表中删除重定向运算符及其操作数。
  6. 执行命令(请参阅执行命令)。
  7. 可选择等待命令完成并收集其退出状态(请参阅退出状态)。

这里发生的是内置函数没有自己的执行环境,所以他们永远看不到修改后的环境。此外,简单的命令(例如 /bin/echo)确实会获得修改后的环境(这就是 env 示例起作用的原因),但是在步骤 #4中,shell 扩展正在当前环境中进行。

换句话说,您没有将 'aaa $TESTVAR ccc' 传递给 /bin/echo;您正在将内插字符串(在当前环境中扩展)传递给 /bin/echo。在这种情况下,由于当前环境没有TESTVAR,您只需将 'aaa ccc' 传递给命令。

概括

文档可能会更清晰。好东西有堆栈溢出!

也可以看看

http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment

于 2012-06-07T20:21:20.490 回答
24

要实现您想要的,请使用

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

原因:

  • 您必须用分号或换行将分配与下一个命令分开,否则在下一个命令(echo)发生参数扩展之前不会执行它。

  • 您需要在子外壳环境中进行分配,以确保它不会持续超出当前行。

该解决方案比其他一些建议的解决方案更短、更整洁、更高效,特别是它不会创建新流程。

于 2013-01-18T14:25:01.817 回答
11

原因是这为一行设置了一个环境变量。但是,echo不做扩展,bash做。因此,您的变量实际上是在命令执行之前展开的,即使它SOME_VARBBB在 echo 命令的上下文中。

要查看效果,您可以执行以下操作:

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB

这里变量在子进程执行之前不会展开,因此您会看到更新后的值。如果您SOME_VARIABLE再次检查父 shell,它仍然是AAA,如预期的那样。

于 2012-06-07T19:31:05.430 回答
11

让我们看一下POSIX 规范,以了解为什么它的行为如此,不仅在 bash 中,而且在任何兼容的 shell 中:


2.10.2、Shell 语法规则

从规则 7(b) 开始,涵盖了赋值在简单命令之前的情况:

如果 '=' 之前的所有字符构成一个有效名称(参见 IEEE Std 1003.1-2001 的基本定义卷,第 3.230 节,名称),则应返回令牌 ASSIGNMENT_WORD。(带引号的字符不能参与形成有效名称。)

[...]

名称的分配应按照简单命令中的规定进行。

因此,对于 POSIX 兼容的 shell,需要解析这个赋值。


2.9.1、简单命令

  1. 重定向应按照重定向中的描述执行。

  2. 在分配值之前,每个变量分配都应针对波浪号扩展、参数扩展、命令替换、算术扩展和引号删除进行扩展。

[...]

如果没有产生命令名,变量赋值将影响当前的执行环境。否则,为命令的执行环境导出变量赋值,并且不影响当前执行环境(特殊内置除外)。如果任何变量赋值尝试为只读变量赋值,则会发生变量赋值错误。有关这些错误的后果,请参阅 Shell 错误的后果。

因此: 必须导出在部分前缀中给简单命令的赋值,并且不得影响“当前 shell 环境”,除非被调用的命令是特殊的内置命令。此外,这些步骤应遵循重定向,这本质上必须在命令调用过程的后期发生。


2.12、Shell执行环境

应在包含以下内容的单独环境中调用除特殊内置程序(参见特殊内置实用程序)以外的实用程序。这些对象的初始值应与父外壳的初始值相同,但以下说明除外。

[...]

具有导出属性的变量,以及在命令期间显式导出的变量,应传递给实用程序环境变量


因此:这些变量在 fork 之后和执行被调用的命令之前由子 shell 扩展,并且必须 - 根据规范 - 单独影响孩子的环境。


现在,对于一些不同的行为:

SOMEVAR=BBB sh -c 'echo "$SOMEVAR"'

...受益于sh实例在启动时从其环境变量(根据 POSIX 规范第 2.5.3 节的要求)创建 shell 变量。


顺便说一句,值得注意的是,您所询问的语法是在一个简单的 command中进行赋值,而不是在subshel​​l中进行赋值。您可以控制管道中涉及的子shell中的分配,如下所示:

{ SOMEVAR=BBB; echo "$SOMEVAR"; } | somecommand ...

...将分配放入运行管道的第一个组件的子外壳中(如果您的外壳确实在子外壳中运行该组件,就POSIX而言,这是未定义的行为;从规范中:“作为扩展,但是,管道中的任何或所有命令都可以在当前环境中执行”)。

于 2015-06-22T15:52:18.033 回答
6

简而言之,在调用命令之前$SOMEVAR评估,而在命令前面添加前缀会修改您正在运行的命令的环境。SOMEVAR=BBB

正如 Charles Duffy 所说,您可以添加一个中间sh过程,该过程将使用类似的语法评估变量,但您可能想要做一些更复杂的事情,如果您仍然遇到问题,了解它会很有用。

于 2015-06-22T19:00:42.910 回答
1
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

用一个 ; 分隔同一行的语句。

于 2012-06-07T19:29:27.363 回答
1

这是另一种选择:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz
于 2014-10-28T17:24:15.163 回答
1
SOMEVAR=BBB echo zzz $SOMEVAR zzz

将添加SOMEVAR=BBB到环境变量中,然后执行echo zzz $SOMEVAR zzz. $SOMEVAR指的是您SOMEVAR预先设置AAA的shell变量。

添加分号SOMEVAR=BBB; echo zzz $SOMEVAR zzz将shell变量设置为BBB,然后执行分号后的命令,即echo zzz $SOMEVAR zzz生成zzz BBB zzz

试试这个命令:

SOMEVAR=BBB env | less

并看看环境。

于 2015-06-22T14:47:14.060 回答