Bash:在命令行上指定echo的环境变量?

sda*_*aau 83 bash environment-variables echo

请考虑以下代码段:

$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz
Run Code Online (Sandbox Code Playgroud)

在这里,我设置$SOMEVARAAA第一线-当我赞同它的第二行,我得到的AAA内容符合市场预期.

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

$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz
Run Code Online (Sandbox Code Playgroud)

......我没有BBB按照我的预期得到 - 我得到旧值(AAA).

这是事情应该如何?如果是这样,那么为什么你可以指定变量LD_PRELOAD=/... program args ...并让它工作?我错过了什么?

Jon*_*ler 92

你看到的是预期的行为.问题是父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)
Run Code Online (Sandbox Code Playgroud)

该变量仅在子shell中设置


Tod*_*obs 33

问题,重新审视

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

任何简单命令或函数的环境[请注意,这不包括内置]可以通过在参数赋值前添加前缀来临时扩充,如Shell参数中所述.这些赋值语句仅影响该命令所见的环境.

如果你真的解析这句话,什么它说的是环境的命令/函数进行了修改,但不适用于父进程的环境.所以,这将工作:

$ TESTVAR=bbb env | fgrep TESTVAR
TESTVAR=bbb
Run Code Online (Sandbox Code Playgroud)

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

$ set -x; TESTVAR=bbb echo aaa $TESTVAR ccc
+ TESTVAR=bbb
+ echo aaa ccc
aaa ccc
Run Code Online (Sandbox Code Playgroud)

因为shell执行参数扩展时.

口译员步骤

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

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

这里发生的事情是内置程序没有自己的执行环境,因此它们永远不会看到修改后的环境.此外,简单的命令(例如/ bin/echo)确实获得了修改的环境(这就是env示例工作的原因)但是shell扩展是在步骤#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


Mar*_*uhn 20

为了达到你想要的效果,请使用

( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )
Run Code Online (Sandbox Code Playgroud)

原因:

  • 必须使用分号或新行与下一个命令分隔赋值,否则在下一个命令(echo)的参数扩展发生之前不会执行该赋值.

  • 您需要在子shell环境中进行分配,以确保它不会超出当前行.

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

  • 对于未来的googlers来说:这可能是这个问题的最佳答案.要进一步复杂化,如果您需要在命令的环境中使用该分配,则需要将其导出.子shell仍然阻止赋值持久化.`(从os import getenv导出SOMEVAR = BBB; python -c"; print getenv('SOMEVAR')") (3认同)

Fat*_*ror 10

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

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

$ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
BBB
Run Code Online (Sandbox Code Playgroud)

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


Cha*_*ffy 9

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


2.10.2、Shell语法规则

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

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

[...]

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

因此,对于符合 POSIX 的 shell 需要解析此分配。


2.9.1、简单命令

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

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

[...]

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

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


2.12、Shell执行环境

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

[...]

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


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


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

SOMEVAR=BBB sh -c 'echo "$SOMEVAR"'
Run Code Online (Sandbox Code Playgroud)

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


顺便说一下,值得注意的是,您所询问的语法是用于简单命令中的赋值,而不是子shell 中的赋值。您可以在管道中涉及的子shell中控制分配,如下所示:

{ SOMEVAR=BBB; echo "$SOMEVAR"; } | somecommand ...
Run Code Online (Sandbox Code Playgroud)

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