变量分配影响当前运行的 shell

8 shell environment-variables posix

在编写一些代码时,我发现这一行:

$ TZ="America/Los_Angeles"       date; echo "$TZ"
Thu Dec 24 14:39:15 PST 2015
Run Code Online (Sandbox Code Playgroud)

正确给出“洛杉矶”的实际时间,并且TZ不保留变量的值。一切都在意料之中。

但是,使用这一行,我曾经扩展了一些格式,并且基本上执行了相同的操作,保留了 TZ 的值:

TZ="America/Los_Angeles" eval  date; echo "$TZ"
Thu Dec 24 14:41:34 PST 2015
America/Los_Angeles
Run Code Online (Sandbox Code Playgroud)

经过多次测试,我发现这种情况只发生在某些 shell 中。它发生在 dash、ksh 中,但不发生在 bash 或 zsh 中。

问的

问题是:

  • 为什么现在的shell中还保留了TZ的值?
  • 如何避免/控制(如果可能)?

额外的。

我用这两行在几个 shell 中运行了测试:

myTZ="America/Los_Angeles"
unset TZ; { TZ="$myTZ"      date; } >/dev/null; echo -n "  direct $TZ"
unset TZ; { TZ="$myTZ" eval date; } >/dev/null; echo    "  evaled $TZ"
Run Code Online (Sandbox Code Playgroud)

结果如下:

/bin/ash        :   direct   evaled America/Los_Angeles
/bin/dash       :   direct   evaled America/Los_Angeles
/bin/sh         :   direct   evaled America/Los_Angeles
/bin/bash       :   direct   evaled
/bin/ksh93      :   direct   evaled America/Los_Angeles
/bin/lksh       :   direct   evaled America/Los_Angeles
/bin/mksh       :   direct   evaled America/Los_Angeles
/bin/zsh        :   direct   evaled
/bin/zsh4       :   direct   evaled 
Run Code Online (Sandbox Code Playgroud)

TZ 值会影响除 bash 和 zsh 之外的所有 shell 中正在运行的 shell。

mik*_*erv 6

正如您所发现的,这是规范的行为。但这也有道理。

该值保留在 shell 的环境中的原因与其他命令在将定义添加到命令行时保留其他环境变量的值相同的原因 - 您在其环境中设置变量。

特殊内建命令通常在任何壳的最内在的品种-eval本质上是shell的分析器的访问名称,set曲目并配置shell选项和参数外壳,return/ break/continue触发回路控制流量,trap手柄信号,exec打开/关闭文件。这些都是基本的实用程序 - 并且通常是通过在外壳的肉和土豆上几乎没有包装器来实现的。

执行大多数命令涉及一些分层环境——一个子shell环境(不一定是一个单独的进程) ——在调用特殊的内置函数时你不会得到它。因此,当您为这些命令之一设置环境时,您就为 shell 设置了环境。因为它们基本上代表了你的外壳。

但它们并不是以这种方式保留环境的唯一命令——函数也有同样的作用。对于特殊的内置程序,错误的行为有所不同 - 尝试cat <doesntexist,然后尝试exec <doesntexist,甚至只是: <doesntexistcat命令会抱怨时,execor:会杀死 POSIX shell。命令行上的扩展错误也是如此。基本上,它们是主循环

这些命令不具有保留的环境-一些炮弹包裹的内部起来更紧密地比别人揭露的核心功能少,加上编程器和接口之间的多个缓冲。这些相同的 shell 也可能比其他 shell 慢一些。当然,它们需要进行大量非标准调整才能使它们符合规范。不管怎样,这不是仿佛这是一个不好的事情:

fn(){ bad_command || return=$some_value return; }
Run Code Online (Sandbox Code Playgroud)

那东西很容易。否则,您将如何保持bad_command如此简单的回报,而不必设置一堆额外的环境,但仍然有条件地进行分配?

arg=$1 shift; x=$y unset y
Run Code Online (Sandbox Code Playgroud)

那种东西也管用。就地交换更简单。

IFS=+  set -- "$IFS" x y z
x="$*" IFS=$1 shift
echo "${x#"$IFS"}" "$*"
Run Code Online (Sandbox Code Playgroud)
+x+y+z x y z
Run Code Online (Sandbox Code Playgroud)

...或者...

expand(){
    PS4="$*" set -x "" "$PS4" 
    { $1; }  2>&1
    PS4=$2   set +x
}   2>/dev/null

x='echo kill my computer; $y'
y='haha! just kidding!' expand "${x##*[\`\(]*}"
Run Code Online (Sandbox Code Playgroud)

...是我喜欢使用的另一种...

fn(){ bad_command || return=$some_value return; }
Run Code Online (Sandbox Code Playgroud)


小智 3

事实证明,这种行为有一个非常具体的原因。
对所发生情况的描述有点长。

只有作业。

(仅)由赋值组成的命令行将为该shell设置变量。

$ unset a b c d
$ a=b c=d
$ echo "<$a::$c>"
<b::d>
Run Code Online (Sandbox Code Playgroud)

分配的变量值将被保留。

外部命令。

外部命令之前的赋值仅适用于shell:

$ unset a b c d
$ a=b c=d bash -c 'echo "one:|$c|"'; echo "two:<$c>"
one:|d|
two:<>
Run Code Online (Sandbox Code Playgroud)

我的意思是“外部”是必须在 PATH 中搜索的任何命令。

这也适用于普通的内置命令(例如 cd):

$ unset a b c d; a=b c=d cd . ; echo "<$a::$c>"
<::>
Run Code Online (Sandbox Code Playgroud)

到目前为止,一切都如通常所预期的那样。

特殊的内置插件。

但对于特殊的内置函数,POSIX 要求为此 shell 设置值

  1. 使用特殊内置实用程序指定的变量分配在内置完成后仍然有效。
$ sh -c 'unset a b c d; a=b c=d export f=g ; echo "<$a::$c::$f>"'
<b::d::g>
Run Code Online (Sandbox Code Playgroud)

我正在使用一个调用来sh假设这sh是一个符合 POSIX 标准的 shell。

这不是通常使用的东西。

这意味着位于任何此特殊内置函数列表前面的赋值应在当前运行的 shell 中保留指定的值:

break : continue . eval exec exit export 
readonly return set shift times trap unset
Run Code Online (Sandbox Code Playgroud)

如果 shell 按照 POSIX 规范工作,就会发生这种情况。

结论:

通过确保命令不是特殊的内置命令,可以仅为一个命令(任何命令)设置变量。该命令command是常规内置命令。它只告诉 shell 使用命令,而不是函数。此行适用于所有 shell(ksh93 除外):

$ unset a b c d; a=b c=d command eval 'f=g'; echo "<$a::$c::$f>"
<::::g>
Run Code Online (Sandbox Code Playgroud)

在这种情况下,变量 a 和 b 是为命令命令的环境设置的,然后被丢弃。

相反,这将保留分配的值(bash 和 zsh 除外):

$ unset a b c d; a=b c=d eval 'f=g'; echo "<$a::$c::$f>"
<b::d::g>
Run Code Online (Sandbox Code Playgroud)

请注意,eval 之后的赋值是单引号的,以保护它免受不必要的扩展。

因此:要将变量放入命令环境中,请使用command eval