Bash 为什么可以';我是否指定一个环境变量并在同一命令行中回显它?

Bash 为什么可以';我是否指定一个环境变量并在同一命令行中回显它?,bash,environment-variables,echo,Bash,Environment Variables,Echo,考虑一下这个片段: $ 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\u PRELOA

考虑一下这个片段:

$ 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\u PRELOAD=/。。。程序参数…
是否正常工作?我遗漏了什么?

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

您的直接选择包括:

  • SOMEVAR=BBB eval echo zzz'$SOMEVAR'zzz
  • SOMEVAR=BBB sh-c'echo zzz$SOMEVAR zzz'
  • 这两种方法都使用单引号来防止父shell计算
    $SOMEVAR
    ;只有在环境中设置后才会对其进行计算(暂时,在单个命令的持续时间内)

    另一种选择是使用子外壳符号(如his中的建议):

    该变量仅在子shell中设置

    SOMEVAR=BBB; echo zzz $SOMEVAR zzz
    

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

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

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

    $ SOME_VAR=BBB bash -c 'echo $SOME_VAR'
    BBB
    
    在这里,直到子进程执行,变量才会展开,因此您可以看到更新的值。如果您在父shell中再次检查
    某个\u变量
    ,它仍然是
    AAA
    ,正如预期的那样。

    问题再次出现 坦率地说,手册在这一点上令人困惑。报告说:

    任何简单命令或函数的环境[请注意,这不包括内置命令]都可以通过在其前面加上参数赋值来临时增强,如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对于它的解释器:

  • 从文件(请参见Shell脚本)和字符串读取其输入 作为-c调用选项的参数提供(请参见调用 Bash),或从用户的终端
  • 按照引用规则将输入分解为单词和运算符 在引用中描述。这些标记由元字符分隔。 别名扩展通过此步骤执行(请参见别名)
  • 将标记解析为简单命令和复合命令(请参见Shell命令)
  • 执行各种外壳扩展(请参见外壳扩展), 将扩展标记拆分为文件名列表(请参见文件名 扩展)以及命令和参数
  • 执行任何必要的重定向(请参阅重定向)并删除 参数列表中的重定向运算符及其操作数
  • 执行命令(请参见执行命令)
  • (可选)等待命令完成并收集其出口 状态(请参见退出状态)
  • 这里发生的事情是,内置程序没有自己的执行环境,所以他们永远看不到修改过的环境。此外,简单的命令(例如/bin/echo)确实会得到一个修改过的ennEnvironment(这就是env示例工作的原因),但是shell扩展是在步骤4中的当前环境中进行的

    换句话说,您没有将“aaa$TESTVAR ccc”传递给/bin/echo;您正在将插值字符串(在当前环境中展开)传递给/bin/echo。在本例中,由于当前环境没有TESTVAR,您只需将“aaa ccc”传递给命令

    总结 文档可以更清晰。幸好堆栈溢出了

    另见

    要实现您的目标,请使用

    ( SOMEVAR=BBB; echo zzz $SOMEVAR zzz )
    
    原因:

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

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


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

    这里有一个替代方案:

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

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

    请尝试以下命令:

    SOMEVAR=BBB env | less
    
    再看看环境。

    让我们来了解一下为什么它不仅在bash中,而且在任何兼容的shell中都会这样:


    2.10.2,Shell语法规则 根据规则7(b),包括在简单命令之前进行赋值的情况:

    <
    SOMEVAR=BBB echo zzz $SOMEVAR zzz
    
    SOMEVAR=BBB env | less
    
    SOMEVAR=BBB sh -c 'echo "$SOMEVAR"'
    
    { SOMEVAR=BBB; echo "$SOMEVAR"; } | somecommand ...