为啥我不能指定环境变量并在同一命令行中回显它?
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' zzz
。
SOMEVAR=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)发生参数扩展之前不会执行。
您需要在 subshell 环境中进行分配,以确保它不会持续超出当前行。
此解决方案比其他一些建议的解决方案更短、更整洁、更高效,特别是它不会创建新流程。
【讨论】:
对于在这里结束的未来谷歌人:这可能是这个问题的最佳答案。更复杂的是,如果您需要分配在命令环境中可用,则需要将其导出。子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_VAR
是 BBB
,您的变量实际上也会在命令执行之前展开。
要查看效果,您可以执行以下操作:
$ 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
看看环境。
【讨论】:
以上是关于为啥我不能指定环境变量并在同一命令行中回显它?的主要内容,如果未能解决你的问题,请参考以下文章