为啥我不能指定环境变量并在同一命令行中回显它?

Posted

技术标签:

【中文标题】为啥我不能指定环境变量并在同一命令行中回显它?【英文标题】:Why can't I specify an environment variable and echo it in the same command line?为什么我不能指定环境变量并在同一命令行中回显它? 【发布时间】:2012-06-11 21:37:51 【问题描述】:

考虑一下这个sn-p:

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

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

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

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

...我没有像预期的那样得到BBB - 我得到了旧值(AAA)。

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

【问题讨论】:

【参考方案1】:

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

您的直接选择包括:

    SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzzSOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'

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

另一种选择是使用 sub-shell 表示法(Marcus Kuhn 在他的answer 中也建议):

(SOMEVAR=BBB; echo zzz $SOMEVAR zzz)

变量只在子shell中设置

【讨论】:

【参考方案2】:

重新审视问题

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

任何简单命令或函数的环境 [注意,这不包括内置函数] 可以通过在其前面加上参数分配来临时扩充,如 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 defines these steps 为其解释器:

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

这里发生的情况是内置函数没有自己的执行环境,因此它们永远不会看到修改后的环境。此外,简单的命令(例如 /bin/echo)do 会获得修改后的环境(这就是 env 示例起作用的原因),但 shell 扩展发生在 current第 4 步中的环境。

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

总结

文档可能会更清晰。好东西还有 Stack Overflow!

另见

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

【讨论】:

我已经对此表示赞同 - 但我刚刚回到这个问题,这篇文章包含我需要的指针;非常感谢,@CodeGnome! 我不知道自发布此答案以来 Bash 在这方面是否发生了变化,但前缀变量赋值 do 现在可以与内置函数一起使用。例如,FOO=foo eval 'echo $FOO' 按预期打印foo。这意味着您可以执行 IFS="..." read ... 之类的操作。 我认为发生的事情是 Bash 实际上临时修改了自己的环境,并在命令完成后恢复它,这可能会产生奇怪的副作用。 所以本质上的问题是,正如 Unix Haters' Handbook 几十年前指出的那样,*nix 从根本上破坏了扩展?【参考方案3】:

要实现你想要的,使用

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )

原因:

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

您需要在 subshel​​l 环境中进行分配,以确保它不会持续超出当前行。

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

【讨论】:

对于在这里结束的未来谷歌人:这可能是这个问题的最佳答案。更复杂的是,如果您需要分配在命令环境中可用,则需要将其导出。子shell 仍然阻止分配持续存在。 (export SOMEVAR=BBB; python -c "from os import getenv; print getenv('SOMEVAR')") @eaj 要将 shell 变量导出到单个外部程序调用,如您的示例所示,只需使用 SOMEVAR=BBB python -c "from os import getenv; print getenv('SOMEVAR')"【参考方案4】:

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

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

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

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

【讨论】:

【参考方案5】:

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


2.10.2,Shell 语法规则

根据规则 7(b),涵盖了赋值位于简单命令之前的情况:

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

[...]

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

因此,对于 POSIX 兼容的 shell,需要解析此分配。


2.9.1,简单命令

    应按照重定向中的说明执行重定向。

    在赋值之前,每个变量赋值都应扩展为波浪号扩展、参数扩展、命令替换、算术扩展和引号删除。

[...]

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

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


2.12、Shell执行环境

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

[...]

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


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


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

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

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


顺便说一句,值得注意的是,您所询问的语法是在一个简单的命令中进行赋值,而不是在一个子shell中进行赋值。您可以像这样控制管道中涉及的子shell中的分配:

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

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

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:
SOMEVAR=BBB; echo zzz $SOMEVAR zzz

使用 ;将同一行的语句分开。

【讨论】:

这行得通,但不是重点。我们的想法是只为一个命令设置环境,而不是像您的解决方案那样永久设置。 感谢@Kyros;不知道为什么我现在错过了:) 仍然在徘徊 LD_PRELOAD 等如何在没有分号的可执行文件前工作,但是......再次感谢 - 干杯! @JonathanLeffler - 确实,这就是想法;我没有意识到分号会使更改永久化 - 感谢您注意到这一点!【参考方案8】:

这是另一种选择:

SOMEVAR=BBB && echo zzz $SOMEVAR zzz

【讨论】:

无论您使用&& 还是; 来分隔命令,分配仍然存在,这不是OP 所希望的行为。 Markus Kuhn 有这个答案的正确版本。【参考方案9】:
SOMEVAR=BBB echo zzz $SOMEVAR zzz

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

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

试试这个命令:

SOMEVAR=BBB env | less

看看环境。

【讨论】:

以上是关于为啥我不能指定环境变量并在同一命令行中回显它?的主要内容,如果未能解决你的问题,请参考以下文章

为啥要设置环境变量,环境变量有啥用

php / html为什么我不能在日期输入字段中回显变量日期?

在 WordPress 中加载模板而不回显它

在WordPress中加载模板而不回显它

在 iframe 中回显变量的正确语法

在批处理脚本中回显到多个文件